From 0d5d5dd4521157ee04121eca86516506bf3185e6 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Thu, 23 Apr 2026 20:18:13 +0000 Subject: [PATCH 01/17] Add Signal TypeScript typings --- packages/schema/index.d.ts | 10 + packages/teamplay/index.d.ts | 52 ++- packages/teamplay/orm/Root.d.ts | 9 + packages/teamplay/orm/Signal.d.ts | 335 ++++++++++++++++++ packages/teamplay/orm/addModel.d.ts | 10 + packages/teamplay/orm/connection.d.ts | 9 + packages/teamplay/orm/getSignal.d.ts | 23 ++ packages/teamplay/orm/index.d.ts | 20 +- packages/teamplay/orm/sub.d.ts | 47 +++ packages/teamplay/package.json | 1 + packages/teamplay/react/helpers.d.ts | 10 + packages/teamplay/react/useSub.d.ts | 92 +++++ .../teamplay/test_types/signal-inference.ts | 188 ++++++++++ packages/teamplay/tsconfig.type-tests.json | 20 ++ packages/utils/accessControl.d.ts | 1 + packages/utils/aggregation.d.ts | 23 ++ plan.md | 85 +++++ 17 files changed, 928 insertions(+), 7 deletions(-) create mode 100644 packages/schema/index.d.ts create mode 100644 packages/teamplay/orm/Root.d.ts create mode 100644 packages/teamplay/orm/Signal.d.ts create mode 100644 packages/teamplay/orm/addModel.d.ts create mode 100644 packages/teamplay/orm/connection.d.ts create mode 100644 packages/teamplay/orm/getSignal.d.ts create mode 100644 packages/teamplay/orm/sub.d.ts create mode 100644 packages/teamplay/react/helpers.d.ts create mode 100644 packages/teamplay/react/useSub.d.ts create mode 100644 packages/teamplay/test_types/signal-inference.ts create mode 100644 packages/teamplay/tsconfig.type-tests.json create mode 100644 packages/utils/accessControl.d.ts create mode 100644 packages/utils/aggregation.d.ts create mode 100644 plan.md diff --git a/packages/schema/index.d.ts b/packages/schema/index.d.ts new file mode 100644 index 0000000..34c3805 --- /dev/null +++ b/packages/schema/index.d.ts @@ -0,0 +1,10 @@ +export const ajv: any +export function transformSchema (schema: any, options?: Record): any +export function onTransformSchema (schema: any): any +export function setOnTransformSchema (fn?: (schema: any) => any): void +export function hasMany (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any +export function hasOne (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any +export function hasManyFlags (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any +export function belongsTo (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any +export const GUID_PATTERN: string +export function pickFormFields (schema: any, fields: string[]): any diff --git a/packages/teamplay/index.d.ts b/packages/teamplay/index.d.ts index 84069cb..bb297e2 100644 --- a/packages/teamplay/index.d.ts +++ b/packages/teamplay/index.d.ts @@ -1,5 +1,46 @@ -// teamplay/index.d.ts import type * as React from 'react' +import type { + CollectionSignalFromSpec, + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + QuerySignal, + Signal, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} from './orm/Signal.js' + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface TeamplayCollections {} + +export interface LocalSignalFactory { + (factory: () => TValue): TypedSignal + (value: TValue): TypedSignal +} + +export type RootCollections = TeamplayCollections> = { + readonly [K in keyof TCollections & string]: CollectionSignalFromSpec +} + +export type RootSignal = TeamplayCollections> = + Signal> & LocalSignalFactory & RootCollections + +export type { + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + QuerySignal, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} export interface ObserverOptions { /** Wrap the resulting component with forwardRef */ @@ -26,10 +67,9 @@ export function observer< options?: ObserverOptions ): React.ComponentType

-// Keep existing public surface available even if typed loosely for now. -export const $: any -export const $root: any -export const model: any +export const $: RootSignal +export const $root: RootSignal +export const model: RootSignal export { default as Signal, SEGMENTS } from './orm/Signal.js' export { __DEBUG_SIGNALS_CACHE__, rawSignal, getSignalClass } from './orm/getSignal.js' export { default as addModel } from './orm/addModel.js' @@ -102,5 +142,5 @@ export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/help export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema' export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation' export { accessControl } from '@teamplay/utils/accessControl' -export function getRootSignal (options?: Record): any +export function getRootSignal = TeamplayCollections> (options?: Record): RootSignal export default $ diff --git a/packages/teamplay/orm/Root.d.ts b/packages/teamplay/orm/Root.d.ts new file mode 100644 index 0000000..5e42821 --- /dev/null +++ b/packages/teamplay/orm/Root.d.ts @@ -0,0 +1,9 @@ +import type { AnySignal } from './Signal.js' + +export const ROOT: unique symbol +export const ROOT_ID: unique symbol +export const ROOT_FUNCTION: unique symbol +export const GLOBAL_ROOT_ID: string + +export function getRootSignal (options?: Record): AnySignal +export function getRoot ($signal: unknown): AnySignal | undefined diff --git a/packages/teamplay/orm/Signal.d.ts b/packages/teamplay/orm/Signal.d.ts new file mode 100644 index 0000000..3021ead --- /dev/null +++ b/packages/teamplay/orm/Signal.d.ts @@ -0,0 +1,335 @@ +export const SEGMENTS: unique symbol +export const ARRAY_METHOD: unique symbol +export const GET: unique symbol +export const GETTERS: unique symbol +export const DEFAULT_GETTERS: readonly string[] + +export type PathSegment = string | number +export type SignalPath = readonly PathSegment[] + +export interface JsonSchemaObject { + readonly type?: string | readonly string[] + readonly properties?: Record + readonly items?: JsonSchema | readonly JsonSchema[] + readonly required?: readonly string[] | boolean + readonly enum?: readonly unknown[] + readonly const?: unknown + readonly additionalProperties?: boolean | JsonSchema + readonly patternProperties?: Record + readonly [keyword: string]: unknown +} + +export type JsonSchema = boolean | JsonSchemaObject + +export interface ZodLikeSchema { + readonly _output?: unknown + readonly _zod?: { + readonly output?: unknown + } +} + +export type InferZodSchema = + TSchema extends { readonly _output?: infer Output } + ? NonNullable + : TSchema extends { readonly _zod?: { readonly output?: infer Output } } + ? NonNullable + : unknown + +type Prettify = { + [K in keyof TValue]: TValue[K] +} + +type PrimitiveFromJsonType = + TType extends 'string' ? string + : TType extends 'number' ? number + : TType extends 'integer' ? number + : TType extends 'boolean' ? boolean + : TType extends 'null' ? null + : unknown + +type JsonTypeIncludes = + TType extends TExpected + ? true + : TType extends readonly unknown[] + ? TExpected extends TType[number] ? true : false + : false + +type IsJsonSchemaKeyword = + TKey extends + | '$id' + | '$schema' + | 'type' + | 'properties' + | 'items' + | 'required' + | 'enum' + | 'const' + | 'additionalProperties' + | 'patternProperties' + | 'description' + | 'title' + | 'default' + | 'errorMessage' + | 'validators' + | 'collection' + | 'format' + | 'minimum' + | 'maximum' + | 'minLength' + | 'maxLength' + | 'minItems' + | 'maxItems' + | 'uniqueItems' + ? true + : false + +type SimplifiedProperties = { + [K in keyof TSchema as K extends string + ? IsJsonSchemaKeyword extends true ? never : K + : never]: TSchema[K] +} + +type HasSimplifiedProperties = + keyof SimplifiedProperties extends never ? false : true + +type SchemaProperties = + TSchema extends { readonly properties?: infer Properties } + ? Properties extends Record + ? Properties + : Record + : HasSimplifiedProperties extends true + ? SimplifiedProperties + : Record + +type ExplicitRequiredKeys = + TSchema extends { readonly required?: infer Required } + ? Required extends readonly string[] + ? Extract + : never + : never + +type SimplifiedRequiredKeys = { + [K in keyof TProperties & string]: TProperties[K] extends { readonly required?: true } ? K : never +}[keyof TProperties & string] + +type RequiredKeys = + ExplicitRequiredKeys | SimplifiedRequiredKeys + +type ObjectFromJsonSchema = + SchemaProperties extends infer Properties + ? Properties extends Record + ? Prettify<{ + [K in RequiredKeys]-?: FromJsonSchema + } & { + [K in Exclude>]?: FromJsonSchema + }> + : Record + : Record + +type ArrayFromJsonSchema = + TSchema extends { readonly items?: infer Items } + ? Items extends readonly [infer First, ...infer Rest] + ? readonly [FromJsonSchema, ...{ [K in keyof Rest]: FromJsonSchema }] + : Items extends JsonSchema + ? Array> + : unknown[] + : unknown[] + +export type FromJsonSchema = + TSchema extends false + ? never + : TSchema extends true + ? unknown + : TSchema extends { readonly const?: infer Const } + ? Const + : TSchema extends { readonly enum?: ReadonlyArray } + ? EnumValue + : TSchema extends { readonly type?: infer Type } + ? JsonTypeIncludes extends true + ? ObjectFromJsonSchema + : JsonTypeIncludes extends true + ? ArrayFromJsonSchema + : PrimitiveFromJsonType + : HasSimplifiedProperties extends true + ? ObjectFromJsonSchema + : unknown + +export type SignalClass = new (segments: PathSegment[]) => Signal + +export type SignalInstance = + TModel extends new (...args: any[]) => infer Instance ? Instance : Signal + +type IsExactlyBaseSignalClass = + [TModel] extends [typeof Signal] + ? [typeof Signal] extends [TModel] + ? true + : false + : false + +type SignalModelInstance = + IsExactlyBaseSignalClass extends true + ? Signal + : Signal & SignalInstance + +export type AnySignal = Signal + +export type TypedSignal< + TValue = unknown, + TModel extends SignalClass = typeof Signal +> = SignalModelInstance & SignalChildren + +export type DocumentSignal< + TValue = unknown, + TModel extends SignalClass = typeof Signal +> = TypedSignal + +export type CollectionSignal< + TDocument = unknown, + TCollectionModel extends SignalClass = typeof Signal, + TDocumentModel extends SignalClass = typeof Signal +> = SignalModelInstance & { + readonly [documentId: string]: DocumentSignal + add: (value: TDocument) => Promise +} + +export type QuerySignal< + TDocument = unknown, + TDocumentModel extends SignalClass = typeof Signal +> = Signal & { + readonly [index: number]: DocumentSignal + [Symbol.iterator]: () => IterableIterator> + map: ( + callback: ( + value: DocumentSignal, + index: number, + array: Array> + ) => TResult + ) => TResult[] + reduce: ( + callback: ( + previousValue: TResult, + currentValue: DocumentSignal, + currentIndex: number, + array: Array> + ) => TResult, + initialValue: TResult + ) => TResult + find: ( + predicate: ( + value: DocumentSignal, + index: number, + obj: Array> + ) => unknown + ) => DocumentSignal | undefined +} + +export type AggregationSignal< + TDocument = unknown, + TDocumentModel extends SignalClass = typeof Signal +> = QuerySignal + +export type CollectionDocument = + TSpec extends CollectionSpec ? Document + : TSpec extends JsonSchema ? FromJsonSchema + : unknown + +export type CollectionDocumentModel = + TSpec extends CollectionSpec ? DocumentModel + : typeof Signal + +export type SignalChild = + DocumentSignal + +export type SignalChildren = + NonNullable extends ReadonlyArray + ? Readonly>> + : NonNullable extends object + ? { + readonly [K in keyof NonNullable & string]-?: SignalChild[K]> + } + : Record + +export interface CollectionSpec< + TDocument = unknown, + TCollectionModel extends SignalClass = typeof Signal, + TDocumentModel extends SignalClass = typeof Signal +> { + readonly document: TDocument + readonly collectionModel: TCollectionModel + readonly documentModel: TDocumentModel +} + +export type JsonSchemaSpec< + TSchema, + TCollectionModel extends SignalClass = typeof Signal, + TDocumentModel extends SignalClass = typeof Signal +> = CollectionSpec, TCollectionModel, TDocumentModel> + +export type ZodSchemaSpec< + TSchema extends ZodLikeSchema, + TCollectionModel extends SignalClass = typeof Signal, + TDocumentModel extends SignalClass = typeof Signal +> = CollectionSpec, TCollectionModel, TDocumentModel> + +export type CollectionSignalFromSpec = + TSpec extends CollectionSpec + ? CollectionSignal + : TSpec extends JsonSchema + ? CollectionSignal> + : CollectionSignal + +export interface Signal { + (...args: never[]): unknown + readonly __valueType?: TValue + readonly [SEGMENTS]: PathSegment[] +} + +export class Signal extends Function { + static ID_FIELDS: readonly string[] + static associations: readonly unknown[] + static addAssociation (association: object): void + static [GETTERS]: readonly string[] + + constructor (segments: PathSegment[]) + + path (): string + leaf (): string + parent (levels?: number): AnySignal + id (): string + batch(fn?: () => TResult): TResult | undefined + get (): TValue + getIds (): Array + peek (): TValue + getId (): string | number + getCollection (): string + getAssociations (): readonly unknown[] + map(callback: (value: AnySignal, index: number, array: AnySignal[]) => TResult): TResult[] + reduce( + callback: (previousValue: TResult, currentValue: AnySignal, currentIndex: number, array: AnySignal[]) => TResult, + initialValue: TResult + ): TResult + find (predicate: (value: AnySignal, index: number, obj: AnySignal[]) => unknown): AnySignal | undefined + set (value: TValue): Promise + assign (value: NonNullable extends object ? Partial> : never): Promise + push (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise + pop (): Promise extends ReadonlyArray ? Item | undefined : unknown> + unshift (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise + shift (): Promise extends ReadonlyArray ? Item | undefined : unknown> + insert (index: number, values: NonNullable extends ReadonlyArray ? Item | Item[] : unknown): Promise + remove (index: number, howMany?: number): Promise + move (from: number, to: number, howMany?: number): Promise + stringInsert (index: number, text: string): Promise + stringRemove (index: number, howMany?: number): Promise + increment (value?: number): Promise + add (value: unknown): Promise + del (): Promise +} + +export const regularBindings: ProxyHandler +export const extremelyLateBindings: ProxyHandler +export function isPublicCollectionSignal ($signal: unknown): boolean +export function isPublicDocumentSignal ($signal: unknown): boolean +export function isPublicCollection (collectionName: unknown): boolean +export function isPrivateCollection (collectionName: unknown): boolean + +export { Signal as default } diff --git a/packages/teamplay/orm/addModel.d.ts b/packages/teamplay/orm/addModel.d.ts new file mode 100644 index 0000000..5758296 --- /dev/null +++ b/packages/teamplay/orm/addModel.d.ts @@ -0,0 +1,10 @@ +import type { SignalClass } from './Signal.js' + +export const MODELS: Record> + +export default function addModel> ( + pattern: string, + Model: TModel +): void + +export function findModel (segments: ReadonlyArray): SignalClass | undefined diff --git a/packages/teamplay/orm/connection.d.ts b/packages/teamplay/orm/connection.d.ts new file mode 100644 index 0000000..0ee5138 --- /dev/null +++ b/packages/teamplay/orm/connection.d.ts @@ -0,0 +1,9 @@ +export function connection (...args: any[]): any +export function setConnection (value: any): void +export function getConnection (): any +export function getDefaultFetchOnly (): boolean +export function setDefaultFetchOnly (value?: boolean): boolean +export function fetchOnly (fn: () => T): T +export function setFetchOnly (value?: boolean): boolean +export function publicOnly (fn: () => T): T +export function setPublicOnly (value?: boolean): boolean diff --git a/packages/teamplay/orm/getSignal.d.ts b/packages/teamplay/orm/getSignal.d.ts new file mode 100644 index 0000000..4a34796 --- /dev/null +++ b/packages/teamplay/orm/getSignal.d.ts @@ -0,0 +1,23 @@ +import type { AnySignal, SignalClass, SignalPath } from './Signal.js' + +export default function getSignal ( + $root?: AnySignal, + segments?: SignalPath, + options?: { + useExtremelyLateBindings?: boolean + rootId?: string + signalHash?: string + proxyHandlers?: ProxyHandler + } +): AnySignal + +export function getSignalClass (segments: SignalPath, rootId?: string): SignalClass +export function rawSignal (proxy: TSignal): TSignal | undefined +// eslint-disable-next-line @typescript-eslint/naming-convention +export const __DEBUG_SIGNALS_CACHE__: { + readonly size: number + get: (key: string) => unknown + set: (key: string, value: unknown, dependencies?: unknown[]) => void + delete: (key: string) => void +} +export function purgeSignalHashes (hashes: Iterable): void diff --git a/packages/teamplay/orm/index.d.ts b/packages/teamplay/orm/index.d.ts index af3d355..a0c0e05 100644 --- a/packages/teamplay/orm/index.d.ts +++ b/packages/teamplay/orm/index.d.ts @@ -1,5 +1,23 @@ -export const BaseModel: any +import type { Signal } from './Signal.js' + +export const BaseModel: typeof Signal export default BaseModel +export { default as Signal } from './Signal.js' +export type { + AggregationSignal, + AnySignal, + CollectionSignal, + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} from './Signal.js' +export type { RootSignal } from '../index.js' export function belongsTo (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any export function hasMany (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any diff --git a/packages/teamplay/orm/sub.d.ts b/packages/teamplay/orm/sub.d.ts new file mode 100644 index 0000000..5694b1b --- /dev/null +++ b/packages/teamplay/orm/sub.d.ts @@ -0,0 +1,47 @@ +import type { + AggregationSignal, + CollectionDocument, + CollectionDocumentModel, + CollectionSignal, + QuerySignal, + Signal +} from './Signal.js' +import type { TeamplayCollections } from '../index.js' + +export default function sub> ( + $signal: TSignal +): TSignal | Promise + +export default function sub any> ( + $collection: CollectionSignal, + params: Record +): QuerySignal | Promise> + +export default function sub ( + $aggregation: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> | Promise, +CollectionDocumentModel +>> + +export default function sub any> ( + $aggregation: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record +): AggregationSignal | Promise> + +export default function sub ( + $signal: TSignal, + params?: TParams +): any diff --git a/packages/teamplay/package.json b/packages/teamplay/package.json index fb1a456..f121e55 100644 --- a/packages/teamplay/package.json +++ b/packages/teamplay/package.json @@ -22,6 +22,7 @@ }, "scripts": { "test": "npm run test-server && npm run test-client", + "test-types": "tsc -p tsconfig.type-tests.json", "test-server": "NODE_OPTIONS=\"--expose-gc\" mocha 'test/[!_]*.js'", "test-server-only": "NODE_OPTIONS=\"--expose-gc\" mocha --grep '@only' 'test/[!_]*.js'", "test-client": "NODE_OPTIONS=\"$NODE_OPTIONS --expose-gc --experimental-vm-modules\" jest", diff --git a/packages/teamplay/react/helpers.d.ts b/packages/teamplay/react/helpers.d.ts new file mode 100644 index 0000000..7a353f0 --- /dev/null +++ b/packages/teamplay/react/helpers.d.ts @@ -0,0 +1,10 @@ +export function useId (): string +export function useNow (interval?: number): number +export function useScheduleUpdate (): (delay?: number) => void +export function useTriggerUpdate (): () => void +export type EffectCleanup = () => void +export type EffectCallback = () => undefined | EffectCleanup + +export function useDidUpdate (fn: EffectCallback, deps?: any[]): void +export function useOnce (condition: any, fn: EffectCallback): void +export function useSyncEffect (fn: EffectCallback, deps?: any[]): void diff --git a/packages/teamplay/react/useSub.d.ts b/packages/teamplay/react/useSub.d.ts new file mode 100644 index 0000000..6a81c7c --- /dev/null +++ b/packages/teamplay/react/useSub.d.ts @@ -0,0 +1,92 @@ +import type { + AggregationSignal, + CollectionDocument, + CollectionDocumentModel, + CollectionSignal, + QuerySignal, + Signal +} from '../orm/Signal.js' +import type { TeamplayCollections } from '../index.js' + +export interface UseSubOptions { + async?: boolean + defer?: boolean | number + batch?: boolean + compatAttemptCleanup?: boolean +} + +export default function useSub> ( + signal: TSignal, + params?: undefined, + options?: UseSubOptions +): TSignal + +export default function useSub any> ( + signal: CollectionSignal, + params: Record, + options?: UseSubOptions +): QuerySignal + +export default function useSub ( + signal: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> + +export default function useSub any> ( + signal: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal + +export default function useSub (signal: any, params?: any, options?: UseSubOptions): any + +export function useAsyncSub> ( + signal: TSignal, + params?: undefined, + options?: UseSubOptions +): TSignal + +export function useAsyncSub any> ( + signal: CollectionSignal, + params: Record, + options?: UseSubOptions +): QuerySignal + +export function useAsyncSub ( + signal: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> + +export function useAsyncSub any> ( + signal: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal + +export function useAsyncSub (signal: any, params?: any, options?: UseSubOptions): any +export function setUseDeferredValue (enabled: boolean): void +export function setDefaultDefer (value?: boolean | number): boolean | number | undefined diff --git a/packages/teamplay/test_types/signal-inference.ts b/packages/teamplay/test_types/signal-inference.ts new file mode 100644 index 0000000..0830e4a --- /dev/null +++ b/packages/teamplay/test_types/signal-inference.ts @@ -0,0 +1,188 @@ +import { + $, + Signal, + addModel, + aggregation, + sub, + useSub, + type FromJsonSchema, + type JsonSchemaSpec, + type QuerySignal, + type ZodSchemaSpec +} from 'teamplay' + +type Equal = + (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) + ? true + : false + +type Expect = T +type AwaitedSub = T extends Promise ? Value : T +type TypeAssertions = [ + GameSchemaInference, + TitleValue, + MaxPlayersValue, + StatusValue, + SubKeepsDocumentModel, + UseSubKeepsDocumentModel, + ZodStructuralInference, + QuerySignalType, + QueryIndexDocumentModel, + QueryIteratorDocumentModel, + HookQueryIndexDocumentModel, + AggregationIndexDocumentModel, + AggregationDocumentMethods, + HookAggregationIndexDocumentModel, + LocalPrimitive, + LocalNestedString, + LocalNestedBoolean, + ComputedNumber, + ComputedString +] + +const gameSchema = { + info: { + type: 'object', + required: true, + properties: { + title: { type: 'string', required: true }, + maxPlayers: { type: 'integer', required: true }, + tags: { + type: 'array', + items: { type: 'string' } + } + } + }, + status: { + type: 'string', + enum: ['draft', 'started'] as const + } +} as const + +interface Game { + info: { + title: string + maxPlayers: number + tags?: string[] + } + status?: 'draft' | 'started' +} + +class GamesModel extends Signal { + findOpenGames () { + return this + } +} + +class GameModel extends Signal { + async start () { + await this.set({ + info: { + title: 'Untitled', + maxPlayers: 4 + }, + status: 'started' + }) + } +} + +addModel('games.*', GameModel) + +interface ZodLikeGame { + _output?: { + info: { + title: string + maxPlayers: number + } + } +} + +declare module 'teamplay' { + interface TeamplayCollections { + games: JsonSchemaSpec + zodGames: ZodSchemaSpec + } +} + +declare const gameId: string + +const $games = $.games +$games.findOpenGames() +$games.add({ + info: { + title: 'Chess', + maxPlayers: 2 + }, + status: 'draft' +}) + +const $game = $.games[gameId] +const $subGame = sub($game) +function useHookGame () { + return useSub($game) +} +const $zodGame = $.zodGames[gameId] +$game.start() +$game.info.title.set('Chess') +$game.info.maxPlayers.increment() +$game.info.tags[0].set('board') + +type GameSchemaInference = Expect, Game>> +type TitleValue = Expect, string>> +type MaxPlayersValue = Expect, number>> +type StatusValue = Expect, 'draft' | 'started' | undefined>> +type SubKeepsDocumentModel = Expect, typeof $game>> +type UseSubKeepsDocumentModel = Expect, typeof $game>> +type ZodStructuralInference = Expect, string>> + +const $queryGames = sub($.games, { status: 'draft' }) +function useHookQueryGames () { + return useSub($.games, { status: 'draft' }) +} +const $$activeGames = aggregation('games', ({ active }: { active: boolean }) => [{ $match: { active } }]) +const $aggregationGames = sub($$activeGames, { active: true }) +function useHookAggregationGames () { + return useSub($$activeGames, { active: true }) +} +const $hookQueryGame = (null as unknown as ReturnType)[0] +const $aggregationGame = (null as unknown as AggregationGames)[0] +const $hookAggregationGame = (null as unknown as ReturnType)[0] + +type QueryGames = AwaitedSub +type AggregationGames = AwaitedSub +type QueryGameItem = QueryGames extends Iterable ? Item : never +type QuerySignalType = Expect>> +type QueryIndexDocumentModel = Expect, string>> +type QueryIteratorDocumentModel = Expect, string>> +type HookQueryIndexDocumentModel = Expect, number>> +type AggregationIndexDocumentModel = Expect, string>> +type AggregationDocumentMethods = Expect, Promise>> +type HookAggregationIndexDocumentModel = Expect, number>> + +const $score = $(0) +$score.increment() + +const $scoreboard = $({ + players: [{ name: 'Robot 1', robot: true }], + totalPlayers: 0, + round: 0 +}) +$scoreboard.players[0].name.set('Robot 2') +$scoreboard.players[0].robot.set(false) +$scoreboard.totalPlayers.increment() + +const $computedScoreboard = $(() => ({ + nextRound: $scoreboard.round.get() + 1, + firstPlayerName: $scoreboard.players[0].name.get() +})) + +type LocalPrimitive = Expect, number>> +const $localPlayer = $scoreboard.players[0] +type LocalNestedString = Expect, string>> +type LocalNestedBoolean = Expect, boolean>> +type ComputedNumber = Expect, number>> +type ComputedString = Expect, string>> + +declare const typeAssertions: TypeAssertions +void typeAssertions diff --git a/packages/teamplay/tsconfig.type-tests.json b/packages/teamplay/tsconfig.type-tests.json new file mode 100644 index 0000000..7219398 --- /dev/null +++ b/packages/teamplay/tsconfig.type-tests.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "types": [ + "react" + ] + }, + "include": [ + "index.d.ts", + "orm/**/*.d.ts", + "react/**/*.d.ts", + "test_types/**/*.ts" + ] +} diff --git a/packages/utils/accessControl.d.ts b/packages/utils/accessControl.d.ts new file mode 100644 index 0000000..b02e71f --- /dev/null +++ b/packages/utils/accessControl.d.ts @@ -0,0 +1 @@ +export function accessControl (...args: any[]): any diff --git a/packages/utils/aggregation.d.ts b/packages/utils/aggregation.d.ts new file mode 100644 index 0000000..37a7259 --- /dev/null +++ b/packages/utils/aggregation.d.ts @@ -0,0 +1,23 @@ +export interface AggregationMeta { + readonly __isAggregation: true + readonly collection: TCollection + readonly name: string +} + +export interface AggregationFunction { + (...args: any[]): any + readonly __isAggregation: true + readonly collection: TCollection +} + +export function aggregation ( + collection: TCollection, + fn: (...args: any[]) => any +): AggregationFunction +export function aggregation (fn: (...args: any[]) => any): AggregationFunction +export function aggregationHeader ( + aggregationMeta: { collection: TCollection, name: string } +): AggregationMeta +export function isAggregationHeader (value: unknown): boolean +export function isAggregationFunction (value: unknown): boolean +export function isClientAggregationFunction (value: unknown): boolean diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..4dc9aa6 --- /dev/null +++ b/plan.md @@ -0,0 +1,85 @@ +# TypeScript Signal Migration Plan + +## Current State + +- The main Signal runtime lives in `packages/teamplay/orm/SignalBase.js`. `packages/teamplay/orm/Signal.js` only switches between `Signal` and `SignalCompat` depending on `TEAMPLAY_COMPAT`. +- The proxy wrapper lives in `packages/teamplay/orm/getSignal.js`. It always uses `extremelyLateBindings` by default, so property access always returns a child signal and method calls are resolved in the proxy `apply` trap. +- Custom model classes are registered with `addModel(pattern, Model)` from `packages/teamplay/orm/addModel.js`. Runtime model selection uses exact segment-length pattern matching with `*`. +- Runtime schema validation is currently backend-only. `@teamplay/backend/features/validateSchema.js` reads `models[collection].schema`, transforms the simplified schema with `@teamplay/schema/transformSchema`, and passes JSON Schema into `@teamplay/sharedb-schema`. +- Public TypeScript coverage is currently very loose. `packages/teamplay/index.d.ts` exports `$`, `model`, `Signal`, `sub`, and hooks mostly as `any`, so VS Code cannot infer collection fields, document fields, or custom model methods. +- The repo has no source build step. Tests import `.js` files directly, so renaming `SignalBase.js` to `.ts` immediately would break runtime unless we also add a build pipeline or commit generated `.js`. + +## Schema Typing Research + +- Plain JSON Schema does not automatically provide TypeScript types unless we either add a type-level JSON Schema mapper or use a library such as `json-schema-to-ts`. +- `json-schema-to-ts` is current at `3.1.1` and is purpose-built for inferring TypeScript from JSON Schema, but adding it to public declarations would make it a public type dependency. +- Zod is current at `4.3.6`. Zod 4 has first-party `z.toJSONSchema()` support, so Zod can be a good developer-facing schema source while still emitting JSON Schema for ShareDB validation. +- For this repository, the lowest-risk first step is to implement a small built-in JSON Schema type mapper that handles Teamplay’s common schemas: object, array, string, number/integer, boolean, null, enum, const, required, and the existing simplified `{ field: schema }` form. +- Zod support should be typed structurally via `_output`/`_zod.output` so users can use Zod schemas for static typing without forcing a hard runtime dependency yet. A later runtime helper can call `z.toJSONSchema()` when Zod is installed. + +## Target Developer UX + +```ts +import { $, Signal, type CollectionSpec, type JsonSchemaSpec, sub } from 'teamplay' + +class GamesModel extends Signal { + findOpenGames () { + return this + } +} + +class GameModel extends Signal { + start () { + return this.status.set('started') + } +} + +const gameSchema = { + info: { + type: 'object', + required: true, + properties: { + title: { type: 'string', required: true }, + maxPlayers: { type: 'integer', required: true } + } + }, + status: { type: 'string', enum: ['draft', 'started'] as const } +} as const + +declare module 'teamplay' { + interface TeamplayCollections { + games: JsonSchemaSpec + } +} + +$.games.findOpenGames() +$.games.gameId.info.title.get() + +const $game = await sub($.games.gameId) +$game.start() +$game.info.maxPlayers.get() +``` + +Expected VS Code behavior: + +- `$.games.` suggests collection model methods, standard Signal methods, and document id access through bracket/dot navigation. +- `$.games[gameId].` suggests document model methods, standard Signal methods, and fields inferred from the schema. +- `$.games[gameId].info.` suggests `title`, `maxPlayers`, and standard Signal methods. +- `sub($.games[gameId])` preserves the same typed document signal. + +## Implementation Strategy + +1. Add strong public declarations around `Signal`, `sub`, `addModel`, and root `$` without changing runtime behavior. +2. Add a `TeamplayCollections` module-augmentation registry. This is necessary because TypeScript cannot infer global `$` types from runtime `addModel()` calls in unrelated files. +3. Add `CollectionSpec`, `JsonSchemaSpec`, and `ZodSchemaSpec` helper types to bind collection/document data and custom collection/document model classes. +4. Add isolated type tests using `tsc --noEmit` against a test-only config under `packages/teamplay`, because the root TypeScript config is currently broken by docs/tooling dependencies. +5. Keep existing JS runtime tests passing, including compatibility tests. +6. In a follow-up source migration, convert `SignalBase.js` to `SignalBase.ts`, set package-local TypeScript compiler options to `module: NodeNext`, emit runtime `.js`, and treat compat files as JS until they are intentionally migrated. + +## Runtime Migration Notes + +- Do not change `extremelyLateBindings` semantics during the typing phase. The runtime method-call behavior depends on the proxy returning child signals for all string properties. +- Keep `SignalCompat` importing the default `Signal` wrapper exactly as it does now so compatibility mode behavior remains unchanged. +- If/when Zod runtime schemas are added, expose a helper that accepts a Zod namespace or converter so `z.toJSONSchema(schema)` can be used without making every runtime consumer load Zod. +- Backend validation should continue to receive plain JSON Schema after `transformSchema()`, regardless of whether the source schema is JSON Schema or Zod. + From 12265fc6f6abdb1a7b47ccde4496b39dcb834559 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Thu, 23 Apr 2026 20:54:31 +0000 Subject: [PATCH 02/17] Convert Signal sources to TypeScript --- packages/schema/index.d.ts | 10 -- packages/schema/{index.js => index.ts} | 2 + packages/schema/package.json | 8 +- packages/teamplay/connect/index.js | 2 +- packages/teamplay/connect/offline/index.js | 2 +- packages/teamplay/connect/test.js | 2 +- packages/teamplay/index.d.ts | 146 ------------------ packages/teamplay/{index.js => index.ts} | 85 ++++++++-- packages/teamplay/orm/$.js | 4 +- packages/teamplay/orm/Aggregation.js | 4 +- packages/teamplay/orm/Compat/SignalCompat.js | 6 +- packages/teamplay/orm/Compat/hooksCompat.js | 4 +- .../teamplay/orm/Compat/queryReadiness.js | 4 +- packages/teamplay/orm/Compat/refFallback.js | 2 +- packages/teamplay/orm/Compat/refRegistry.js | 2 +- .../teamplay/orm/Compat/startStopCompat.js | 2 +- packages/teamplay/orm/Doc.js | 6 +- packages/teamplay/orm/Query.js | 6 +- packages/teamplay/orm/Reaction.js | 4 +- packages/teamplay/orm/Root.d.ts | 9 -- packages/teamplay/orm/{Root.js => Root.ts} | 4 +- packages/teamplay/orm/Signal.js | 22 --- .../teamplay/orm/{Signal.d.ts => Signal.ts} | 85 +++------- .../orm/{SignalBase.js => SignalBase.ts} | 137 ++++++++++------ packages/teamplay/orm/Value.js | 4 +- packages/teamplay/orm/addModel.d.ts | 10 -- .../teamplay/orm/{addModel.js => addModel.ts} | 2 + packages/teamplay/orm/connection.d.ts | 9 -- .../orm/{connection.js => connection.ts} | 2 + packages/teamplay/orm/dataTree.js | 2 +- packages/teamplay/orm/disposeRootContext.js | 2 +- packages/teamplay/orm/getSignal.d.ts | 23 --- .../orm/{getSignal.js => getSignal.ts} | 10 +- packages/teamplay/orm/idFields.js | 2 +- packages/teamplay/orm/index.d.ts | 24 --- packages/teamplay/orm/index.js | 5 - packages/teamplay/orm/index.ts | 23 +++ packages/teamplay/orm/rootContext.js | 2 +- packages/teamplay/orm/rootScope.js | 2 +- packages/teamplay/orm/sub.d.ts | 47 ------ packages/teamplay/orm/{sub.js => sub.ts} | 60 ++++++- packages/teamplay/package.json | 20 ++- packages/teamplay/react/convertToObserver.js | 2 +- packages/teamplay/react/helpers.d.ts | 10 -- .../teamplay/react/{helpers.js => helpers.ts} | 2 + packages/teamplay/react/universal$.js | 2 +- packages/teamplay/react/universalSub.js | 2 +- packages/teamplay/react/useSub.d.ts | 92 ----------- .../teamplay/react/{useSub.js => useSub.ts} | 94 ++++++++++- packages/teamplay/react/useSuspendMemo.js | 2 +- packages/teamplay/react/wrapIntoSuspense.js | 2 +- packages/teamplay/server.js | 2 +- packages/teamplay/test/$.js | 4 +- packages/teamplay/test/_helpers.js | 2 +- packages/teamplay/test/aggregationEvents.js | 2 +- .../teamplay/test/constructorStaticAccess.js | 4 +- packages/teamplay/test/dotSyntax.js | 2 +- packages/teamplay/test/gcCleanup.js | 6 +- packages/teamplay/test/getCollectionCompat.js | 4 +- packages/teamplay/test/idFields.js | 4 +- .../teamplay/test/missingDocPlaceholder.js | 2 +- packages/teamplay/test/ormAssociations.js | 4 +- .../test/publicDocCreateConsistency.js | 2 +- packages/teamplay/test/publicOnlyCompat.js | 2 +- packages/teamplay/test/queryEvents.js | 4 +- packages/teamplay/test/rootClose.js | 4 +- packages/teamplay/test/rootFetchOnly.js | 4 +- packages/teamplay/test/rootFinalization.js | 4 +- packages/teamplay/test/rootScopeHelpers.js | 2 +- .../teamplay/test/rootScopedPrivateStorage.js | 2 +- .../teamplay/test/rootScopedPublicSignals.js | 4 +- .../teamplay/test/rootScopedRefsAndEvents.js | 2 +- packages/teamplay/test/signalCompat.js | 10 +- packages/teamplay/test/sub$.js | 6 +- .../teamplay/test/subscriptionManagers.js | 8 +- packages/teamplay/test/ts-transform.cjs | 17 ++ .../teamplay/test_client/react-extended.js | 8 +- packages/teamplay/test_client/react-gc.js | 2 +- .../test_client/react-subscriptions.js | 2 +- packages/teamplay/test_client/react.js | 4 +- .../test_client/session-ref-compat.js | 4 +- packages/teamplay/tsconfig.type-tests.json | 12 +- packages/utils/accessControl.d.ts | 1 - .../{accessControl.js => accessControl.ts} | 2 + packages/utils/aggregation.d.ts | 23 --- .../utils/{aggregation.js => aggregation.ts} | 32 +++- packages/utils/package.json | 10 +- plan.md | 6 + 88 files changed, 553 insertions(+), 675 deletions(-) delete mode 100644 packages/schema/index.d.ts rename packages/schema/{index.js => index.ts} (84%) delete mode 100644 packages/teamplay/index.d.ts rename packages/teamplay/{index.js => index.ts} (57%) delete mode 100644 packages/teamplay/orm/Root.d.ts rename packages/teamplay/orm/{Root.js => Root.ts} (96%) delete mode 100644 packages/teamplay/orm/Signal.js rename packages/teamplay/orm/{Signal.d.ts => Signal.ts} (75%) rename packages/teamplay/orm/{SignalBase.js => SignalBase.ts} (83%) delete mode 100644 packages/teamplay/orm/addModel.d.ts rename packages/teamplay/orm/{addModel.js => addModel.ts} (94%) delete mode 100644 packages/teamplay/orm/connection.d.ts rename packages/teamplay/orm/{connection.js => connection.ts} (93%) delete mode 100644 packages/teamplay/orm/getSignal.d.ts rename packages/teamplay/orm/{getSignal.js => getSignal.ts} (94%) delete mode 100644 packages/teamplay/orm/index.d.ts delete mode 100644 packages/teamplay/orm/index.js create mode 100644 packages/teamplay/orm/index.ts delete mode 100644 packages/teamplay/orm/sub.d.ts rename packages/teamplay/orm/{sub.js => sub.ts} (74%) delete mode 100644 packages/teamplay/react/helpers.d.ts rename packages/teamplay/react/{helpers.js => helpers.ts} (98%) delete mode 100644 packages/teamplay/react/useSub.d.ts rename packages/teamplay/react/{useSub.js => useSub.ts} (70%) create mode 100644 packages/teamplay/test/ts-transform.cjs delete mode 100644 packages/utils/accessControl.d.ts rename packages/utils/{accessControl.js => accessControl.ts} (92%) delete mode 100644 packages/utils/aggregation.d.ts rename packages/utils/{aggregation.js => aggregation.ts} (65%) diff --git a/packages/schema/index.d.ts b/packages/schema/index.d.ts deleted file mode 100644 index 34c3805..0000000 --- a/packages/schema/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const ajv: any -export function transformSchema (schema: any, options?: Record): any -export function onTransformSchema (schema: any): any -export function setOnTransformSchema (fn?: (schema: any) => any): void -export function hasMany (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export function hasOne (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export function hasManyFlags (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export function belongsTo (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export const GUID_PATTERN: string -export function pickFormFields (schema: any, fields: string[]): any diff --git a/packages/schema/index.js b/packages/schema/index.ts similarity index 84% rename from packages/schema/index.js rename to packages/schema/index.ts index 0c38212..16b335f 100644 --- a/packages/schema/index.js +++ b/packages/schema/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck export { default as ajv } from './lib/ajv.js' export { default as transformSchema } from './lib/transformSchema.js' export { onTransformSchema, setOnTransformSchema } from './lib/onTransformSchema.js' diff --git a/packages/schema/package.json b/packages/schema/package.json index ba59b8b..cadea20 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,9 +3,13 @@ "type": "module", "version": "0.4.0-alpha.9", "description": "Utils to work with json-schema", - "main": "index.js", + "main": "index.ts", + "types": "index.ts", "exports": { - ".": "./index.js" + ".": { + "types": "./index.ts", + "default": "./index.ts" + } }, "dependencies": { "ajv": "^8.12.0", diff --git a/packages/teamplay/connect/index.js b/packages/teamplay/connect/index.js index 15f3752..1096327 100644 --- a/packages/teamplay/connect/index.js +++ b/packages/teamplay/connect/index.js @@ -1,6 +1,6 @@ import Socket from '@teamplay/channel' import Connection from './sharedbConnection.cjs' -import { connection, setConnection } from '../orm/connection.js' +import { connection, setConnection } from '../orm/connection.ts' export default function connect (options) { if (connection) return diff --git a/packages/teamplay/connect/offline/index.js b/packages/teamplay/connect/offline/index.js index 22c4b35..821f455 100644 --- a/packages/teamplay/connect/offline/index.js +++ b/packages/teamplay/connect/offline/index.js @@ -2,7 +2,7 @@ // This creates a full sharedb server with mingo database in the browser or react-native app. import ShareDbMingo from '@startupjs/sharedb-mingo-memory' import ShareBackend from 'sharedb' -import { connection, setConnection } from '../../orm/connection.js' +import { connection, setConnection } from '../../orm/connection.ts' const STORAGE_NAMESPACE = 'teamplay-offline' const DOCS_PREFIX = `${STORAGE_NAMESPACE}:docs:` diff --git a/packages/teamplay/connect/test.js b/packages/teamplay/connect/test.js index ae46471..5b3e27e 100644 --- a/packages/teamplay/connect/test.js +++ b/packages/teamplay/connect/test.js @@ -3,7 +3,7 @@ // and creates a server connection to it. import ShareDbMingo from '@startupjs/sharedb-mingo-memory' import ShareBackend from 'sharedb' -import { connection, setConnection } from '../orm/connection.js' +import { connection, setConnection } from '../orm/connection.ts' export default function connect () { if (connection) return diff --git a/packages/teamplay/index.d.ts b/packages/teamplay/index.d.ts deleted file mode 100644 index bb297e2..0000000 --- a/packages/teamplay/index.d.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type * as React from 'react' -import type { - CollectionSignalFromSpec, - CollectionSpec, - DocumentSignal, - FromJsonSchema, - JsonSchema, - JsonSchemaSpec, - QuerySignal, - Signal, - SignalClass, - TypedSignal, - ZodLikeSchema, - ZodSchemaSpec -} from './orm/Signal.js' - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface TeamplayCollections {} - -export interface LocalSignalFactory { - (factory: () => TValue): TypedSignal - (value: TValue): TypedSignal -} - -export type RootCollections = TeamplayCollections> = { - readonly [K in keyof TCollections & string]: CollectionSignalFromSpec -} - -export type RootSignal = TeamplayCollections> = - Signal> & LocalSignalFactory & RootCollections - -export type { - CollectionSpec, - DocumentSignal, - FromJsonSchema, - JsonSchema, - JsonSchemaSpec, - QuerySignal, - SignalClass, - TypedSignal, - ZodLikeSchema, - ZodSchemaSpec -} - -export interface ObserverOptions { - /** Wrap the resulting component with forwardRef */ - forwardRef?: boolean - /** Enable/disable the internal cache (default: true) */ - cache?: boolean - /** Milliseconds or boolean to throttle reactive updates */ - throttle?: number | boolean - /** Pass-through flag consumed by wrapIntoSuspense */ - defer?: boolean | number - /** Props forwarded to React.Suspense (fallback required internally) */ - suspenseProps?: React.ComponentProps -} - -/** - * Makes any React component reactive and Suspense-aware. - * Props are passed through unchanged; the returned component - * preserves the original type so consumers keep full typings. - */ -export function observer< - P extends Record = Record -> ( - component: React.ComponentType

, - options?: ObserverOptions -): React.ComponentType

- -export const $: RootSignal -export const $root: RootSignal -export const model: RootSignal -export { default as Signal, SEGMENTS } from './orm/Signal.js' -export { __DEBUG_SIGNALS_CACHE__, rawSignal, getSignalClass } from './orm/getSignal.js' -export { default as addModel } from './orm/addModel.js' -export { default as signal } from './orm/getSignal.js' -export { GLOBAL_ROOT_ID } from './orm/Root.js' -export { default as sub } from './orm/sub.js' -export { - default as useSub, - useAsyncSub, - setUseDeferredValue as __setUseDeferredValue, - setDefaultDefer as __setDefaultDefer -} from './react/useSub.js' -export function useSuspendMemo (factory: () => T, deps?: any[]): T -export function useSuspendMemoByKey (key: any, factory: () => T, deps?: any[]): T -export function useValue (defaultValue?: any): [any, any] -export function useValue$ (defaultValue?: any): any -export function useModel (path?: any): any -export function useLocal (path?: any): [any, any] -export function useLocal$ (path?: any): any -export function useSession (path?: any): [any, any] -export function useSession$ (path?: any): any -export function usePage (path?: any): [any, any] -export function usePage$ (path?: any): any -export function useBatch (): void -export function useDoc (collection: string, id: any, options?: any): [any, any] -export function useDoc$ (collection: string, id: any, options?: any): any -export function useBatchDoc (collection: string, id: any, options?: any): [any, any] -export function useBatchDoc$ (collection: string, id: any, options?: any): any -export function useAsyncDoc (collection: string, id: any, options?: any): [any, any] -export function useAsyncDoc$ (collection: string, id: any, options?: any): any -export function useQuery (collection: string, query: any, options?: any): [any, any] -export function useQuery$ (collection: string, query: any, options?: any): any -export function useAsyncQuery (collection: string, query: any, options?: any): [any, any] -export function useAsyncQuery$ (collection: string, query: any, options?: any): any -export function useBatchQuery (collection: string, query: any, options?: any): [any, any] -export function useBatchQuery$ (collection: string, query: any, options?: any): any -export function useQueryIds (collection: string, ids?: any[], options?: any): [any, any] -export function useBatchQueryIds (collection: string, ids?: any[], options?: any): [any, any] -export function useAsyncQueryIds (collection: string, ids?: any[], options?: any): [any, any] -export function useQueryDoc (collection: string, query: any, options?: any): [any, any] -export function useQueryDoc$ (collection: string, query: any, options?: any): any -export function useBatchQueryDoc (collection: string, query: any, options?: any): [any, any] -export function useBatchQueryDoc$ (collection: string, query: any, options?: any): any -export function useAsyncQueryDoc (collection: string, query: any, options?: any): [any, any] -export function useAsyncQueryDoc$ (collection: string, query: any, options?: any): any -export function useLocalDoc (collection: string, id: any): [any, any] -export function useLocalDoc$ (collection: string, id: any): any -export function emit (eventName: string, ...args: any[]): void -export function useOn ( - eventName: 'change' | 'all', - pattern: string | { path: () => string }, - handler: (...args: any[]) => void, - deps?: any[] -): void -export function useOn (eventName: string, handler: (...args: any[]) => void, deps?: any[]): void -export function useEmit (): (eventName: string, ...args: any[]) => void -export function batch (fn?: () => T): T | undefined -export function batchModel (fn?: () => T): T | undefined -export function clone (value: T): T -export function initLocalCollection (name: string): any -export function useApi (api: (...args: any[]) => any, args?: any[], options?: { debounce?: number }): [any, boolean, any] -type EffectCleanup = (() => void) | undefined -export function useDidUpdate (fn: () => EffectCleanup, deps?: any[]): void -export function useOnce (condition: any, fn: () => EffectCleanup): void -export function useSyncEffect (fn: () => EffectCleanup, deps?: any[]): void -export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js' -export function getSubscriptionGcDelay (): number -export function setSubscriptionGcDelay (ms?: number | null): number -export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js' -export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema' -export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation' -export { accessControl } from '@teamplay/utils/accessControl' -export function getRootSignal = TeamplayCollections> (options?: Record): RootSignal -export default $ diff --git a/packages/teamplay/index.js b/packages/teamplay/index.ts similarity index 57% rename from packages/teamplay/index.js rename to packages/teamplay/index.ts index 40bddf0..abcaee7 100644 --- a/packages/teamplay/index.js +++ b/packages/teamplay/index.ts @@ -1,28 +1,81 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck // NOTE: // $() and sub() are currently set to be universal ones which work in both // plain JS and React environments. In React they are tied to the observer() HOC. // This is done to simplify the API. // In future, we might want to separate the plain JS and React APIs -import { getRootSignal as _getRootSignal, GLOBAL_ROOT_ID } from './orm/Root.js' +import type * as React from 'react' +import { getRootSignal as _getRootSignal, GLOBAL_ROOT_ID } from './orm/Root.ts' import universal$ from './react/universal$.js' import useApi from './react/useApi.js' +import type { + CollectionSignalFromSpec, + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + QuerySignal, + Signal, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} from './orm/Signal.ts' -export { default as Signal, SEGMENTS } from './orm/Signal.js' -export { __DEBUG_SIGNALS_CACHE__, rawSignal, getSignalClass } from './orm/getSignal.js' -export { default as addModel } from './orm/addModel.js' -export { default as signal } from './orm/getSignal.js' -export { GLOBAL_ROOT_ID } from './orm/Root.js' -export const $ = _getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ }) -export const $root = $ -export const model = $ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface TeamplayCollections {} + +export interface LocalSignalFactory { + (factory: () => TValue): TypedSignal + (value: TValue): TypedSignal +} + +export type RootCollections = TeamplayCollections> = { + readonly [K in keyof TCollections & string]: CollectionSignalFromSpec +} + +export type RootSignal = TeamplayCollections> = + Signal> & LocalSignalFactory & RootCollections + +export type { + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + QuerySignal, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} + +export interface ObserverOptions { + forwardRef?: boolean + cache?: boolean + throttle?: number | boolean + defer?: boolean | number + suspenseProps?: React.ComponentProps +} + +export { default as Signal, SEGMENTS } from './orm/Signal.ts' +export { __DEBUG_SIGNALS_CACHE__, rawSignal, getSignalClass } from './orm/getSignal.ts' +export { default as addModel } from './orm/addModel.ts' +export { default as signal } from './orm/getSignal.ts' +export { GLOBAL_ROOT_ID } from './orm/Root.ts' +export const $: RootSignal = _getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ }) as RootSignal +export const $root: RootSignal = $ +export const model: RootSignal = $ export default $ -export { default as sub } from './orm/sub.js' +export { default as sub } from './orm/sub.ts' export { default as useSub, useAsyncSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer -} from './react/useSub.js' +} from './react/useSub.ts' export { default as useSuspendMemo, useSuspendMemoByKey @@ -68,7 +121,7 @@ export { useDidUpdate, useOnce, useSyncEffect -} from './react/helpers.js' +} from './react/helpers.ts' export { connection, setConnection, @@ -77,9 +130,9 @@ export { setDefaultFetchOnly, publicOnly, setPublicOnly -} from './orm/connection.js' +} from './orm/connection.ts' export { getSubscriptionGcDelay, setSubscriptionGcDelay } from './orm/subscriptionGcDelay.js' -export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js' +export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.ts' export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema' export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation' export { accessControl } from '@teamplay/utils/accessControl' @@ -117,9 +170,9 @@ export function initLocalCollection (name) { export { useApi } -export function getRootSignal (options) { +export function getRootSignal = TeamplayCollections> (options?: Record): RootSignal { return _getRootSignal({ rootFunction: universal$, ...options - }) + }) as RootSignal } diff --git a/packages/teamplay/orm/$.js b/packages/teamplay/orm/$.js index fae53dd..ba61fbe 100644 --- a/packages/teamplay/orm/$.js +++ b/packages/teamplay/orm/$.js @@ -1,8 +1,8 @@ // this is just the $() function implementation. // The actual $ exported from this package is a Proxy targeting the dataTree root, // and this function is an implementation of the `apply` handler for that Proxy. -import getSignal from './getSignal.js' -import Signal from './Signal.js' +import getSignal from './getSignal.ts' +import Signal from './Signal.ts' import { LOCAL, valueSubscriptions } from './Value.js' import { reactionSubscriptions } from './Reaction.js' diff --git a/packages/teamplay/orm/Aggregation.js b/packages/teamplay/orm/Aggregation.js index cc0deea..28378ec 100644 --- a/packages/teamplay/orm/Aggregation.js +++ b/packages/teamplay/orm/Aggregation.js @@ -1,6 +1,6 @@ import { raw } from '@nx-js/observer-util' import { getRaw } from './dataTree.js' -import getSignal from './getSignal.js' +import getSignal from './getSignal.ts' import { QuerySubscriptions, hashQuery, @@ -10,7 +10,7 @@ import { COLLECTION_NAME, parseQueryHash } from './Query.js' -import Signal, { SEGMENTS } from './Signal.js' +import Signal, { SEGMENTS } from './Signal.ts' import { getIdFieldsForSegments, isPlainObject } from './idFields.js' import { delPrivateData, getPrivateData, setPrivateData } from './privateData.js' diff --git a/packages/teamplay/orm/Compat/SignalCompat.js b/packages/teamplay/orm/Compat/SignalCompat.js index fffc715..50bf0d8 100644 --- a/packages/teamplay/orm/Compat/SignalCompat.js +++ b/packages/teamplay/orm/Compat/SignalCompat.js @@ -8,9 +8,9 @@ import { isPublicCollection, isPublicCollectionSignal, isPublicDocumentSignal -} from '../SignalBase.js' -import { getRoot, ROOT, ROOT_ID, getRootSignal, GLOBAL_ROOT_ID, unregisterRootFinalizer } from '../Root.js' -import { isPrivateMutationForbidden } from '../connection.js' +} from '../SignalBase.ts' +import { getRoot, ROOT, ROOT_ID, getRootSignal, GLOBAL_ROOT_ID, unregisterRootFinalizer } from '../Root.ts' +import { isPrivateMutationForbidden } from '../connection.ts' import { docSubscriptions } from '../Doc.js' import { IS_QUERY, getQuerySignal, querySubscriptions } from '../Query.js' import { IS_AGGREGATION, aggregationSubscriptions, getAggregationSignal } from '../Aggregation.js' diff --git a/packages/teamplay/orm/Compat/hooksCompat.js b/packages/teamplay/orm/Compat/hooksCompat.js index 2cdd8d6..d00d60c 100644 --- a/packages/teamplay/orm/Compat/hooksCompat.js +++ b/packages/teamplay/orm/Compat/hooksCompat.js @@ -1,5 +1,5 @@ -import { getRootSignal, GLOBAL_ROOT_ID } from '../Root.js' -import useSub, { useAsyncSub } from '../../react/useSub.js' +import { getRootSignal, GLOBAL_ROOT_ID } from '../Root.ts' +import useSub, { useAsyncSub } from '../../react/useSub.ts' import universal$ from '../../react/universal$.js' import * as promiseBatcher from '../../react/promiseBatcher.js' import { isCompatEnv } from '../compatEnv.js' diff --git a/packages/teamplay/orm/Compat/queryReadiness.js b/packages/teamplay/orm/Compat/queryReadiness.js index 5ccf983..3540031 100644 --- a/packages/teamplay/orm/Compat/queryReadiness.js +++ b/packages/teamplay/orm/Compat/queryReadiness.js @@ -1,10 +1,10 @@ import { getRaw } from '../dataTree.js' -import { getConnection } from '../connection.js' +import { getConnection } from '../connection.ts' import { isMissingShareDoc } from '../missingDoc.js' import { QUERIES, HASH, PARAMS, COLLECTION_NAME, querySubscriptions } from '../Query.js' import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../Aggregation.js' import { getPrivateData, setPrivateData } from '../privateData.js' -import { getRoot, ROOT_ID } from '../Root.js' +import { getRoot, ROOT_ID } from '../Root.ts' import { isRootContextClosed } from '../rootContext.js' import { getScopedSignalHash, normalizeRootId } from '../rootScope.js' diff --git a/packages/teamplay/orm/Compat/refFallback.js b/packages/teamplay/orm/Compat/refFallback.js index 492c2e4..3e928a8 100644 --- a/packages/teamplay/orm/Compat/refFallback.js +++ b/packages/teamplay/orm/Compat/refFallback.js @@ -1,5 +1,5 @@ import { getRefLinks } from './refRegistry.js' -import { GLOBAL_ROOT_ID } from '../Root.js' +import { GLOBAL_ROOT_ID } from '../Root.ts' export const REF_TARGET = Symbol.for('teamplay.compat.refTarget') diff --git a/packages/teamplay/orm/Compat/refRegistry.js b/packages/teamplay/orm/Compat/refRegistry.js index 97b2af7..3bc7208 100644 --- a/packages/teamplay/orm/Compat/refRegistry.js +++ b/packages/teamplay/orm/Compat/refRegistry.js @@ -1,4 +1,4 @@ -import { GLOBAL_ROOT_ID } from '../Root.js' +import { GLOBAL_ROOT_ID } from '../Root.ts' import { normalizeRootId } from '../rootScope.js' import { getRootContext, getRootContexts } from '../rootContext.js' diff --git a/packages/teamplay/orm/Compat/startStopCompat.js b/packages/teamplay/orm/Compat/startStopCompat.js index 1641544..4f71225 100644 --- a/packages/teamplay/orm/Compat/startStopCompat.js +++ b/packages/teamplay/orm/Compat/startStopCompat.js @@ -1,5 +1,5 @@ import { observe, raw, unobserve } from '@nx-js/observer-util' -import { getRoot } from '../Root.js' +import { getRoot } from '../Root.ts' import { scheduleReaction } from '../batchScheduler.js' const START_REACTIONS = Symbol('compat start reactions') diff --git a/packages/teamplay/orm/Doc.js b/packages/teamplay/orm/Doc.js index a7a7b9e..2a421cb 100644 --- a/packages/teamplay/orm/Doc.js +++ b/packages/teamplay/orm/Doc.js @@ -1,14 +1,14 @@ import { isObservable, observable, raw } from '@nx-js/observer-util' import { set as _set, del as _del, getRaw as _getRaw } from './dataTree.js' -import { SEGMENTS } from './Signal.js' -import { getConnection } from './connection.js' +import { SEGMENTS } from './Signal.ts' +import { getConnection } from './connection.ts' import FinalizationRegistry from '../utils/MockFinalizationRegistry.js' import SubscriptionState from './SubscriptionState.js' import { getIdFieldsForSegments, injectIdFields, isPlainObject } from './idFields.js' import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js' import { getSubscriptionGcDelay } from './subscriptionGcDelay.js' import { isMissingShareDoc } from './missingDoc.js' -import { getRoot, ROOT_ID, GLOBAL_ROOT_ID, getRootTransportMode } from './Root.js' +import { getRoot, ROOT_ID, GLOBAL_ROOT_ID, getRootTransportMode } from './Root.ts' import { registerRootOwnedDirectDocSubscription, unregisterRootOwnedDirectDocSubscription, diff --git a/packages/teamplay/orm/Query.js b/packages/teamplay/orm/Query.js index 3b6bd9f..cc21372 100644 --- a/packages/teamplay/orm/Query.js +++ b/packages/teamplay/orm/Query.js @@ -1,7 +1,7 @@ import { raw } from '@nx-js/observer-util' import { set as _set, getRaw } from './dataTree.js' -import getSignal from './getSignal.js' -import { getConnection } from './connection.js' +import getSignal from './getSignal.ts' +import { getConnection } from './connection.ts' import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js' import { isCompatEnv } from './compatEnv.js' import { docSubscriptions } from './Doc.js' @@ -10,7 +10,7 @@ import SubscriptionState from './SubscriptionState.js' import { getIdFieldsForSegments, injectIdFields, isPlainObject } from './idFields.js' import { getSubscriptionGcDelay } from './subscriptionGcDelay.js' import { getScopedSignalHash, normalizeRootId } from './rootScope.js' -import { getRoot, ROOT_ID, getRootTransportMode } from './Root.js' +import { getRoot, ROOT_ID, getRootTransportMode } from './Root.ts' import { registerRootOwnedRuntime, unregisterRootOwnedRuntime } from './rootContext.js' import { delPrivateData, diff --git a/packages/teamplay/orm/Reaction.js b/packages/teamplay/orm/Reaction.js index 5405062..5e5a0fe 100644 --- a/packages/teamplay/orm/Reaction.js +++ b/packages/teamplay/orm/Reaction.js @@ -1,9 +1,9 @@ import { observe, unobserve } from '@nx-js/observer-util' -import { SEGMENTS } from './Signal.js' +import { SEGMENTS } from './Signal.ts' import { LOCAL } from './Value.js' import FinalizationRegistry from '../utils/MockFinalizationRegistry.js' import { scheduleReaction } from './batchScheduler.js' -import { getRoot, ROOT_ID } from './Root.js' +import { getRoot, ROOT_ID } from './Root.ts' import { delPrivateData, setPrivateData } from './privateData.js' // this is `let` to be able to directly change it if needed in tests or in the app diff --git a/packages/teamplay/orm/Root.d.ts b/packages/teamplay/orm/Root.d.ts deleted file mode 100644 index 5e42821..0000000 --- a/packages/teamplay/orm/Root.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { AnySignal } from './Signal.js' - -export const ROOT: unique symbol -export const ROOT_ID: unique symbol -export const ROOT_FUNCTION: unique symbol -export const GLOBAL_ROOT_ID: string - -export function getRootSignal (options?: Record): AnySignal -export function getRoot ($signal: unknown): AnySignal | undefined diff --git a/packages/teamplay/orm/Root.js b/packages/teamplay/orm/Root.ts similarity index 96% rename from packages/teamplay/orm/Root.js rename to packages/teamplay/orm/Root.ts index 1b82090..a82efaf 100644 --- a/packages/teamplay/orm/Root.js +++ b/packages/teamplay/orm/Root.ts @@ -1,4 +1,6 @@ -import getSignal from './getSignal.js' +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck +import getSignal from './getSignal.ts' import disposeRootContext from './disposeRootContext.js' import { getRootContext, reviveRootContext } from './rootContext.js' import { isGlobalRootId, normalizeRootId } from './rootScope.js' diff --git a/packages/teamplay/orm/Signal.js b/packages/teamplay/orm/Signal.js deleted file mode 100644 index 04ae043..0000000 --- a/packages/teamplay/orm/Signal.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Signal } from './SignalBase.js' -import SignalCompat from './Compat/SignalCompat.js' -import { isCompatEnv } from './compatEnv.js' - -export { - Signal, - SEGMENTS, - ARRAY_METHOD, - GET, - GETTERS, - DEFAULT_GETTERS, - regularBindings, - extremelyLateBindings, - isPublicCollectionSignal, - isPublicDocumentSignal, - isPublicCollection, - isPrivateCollection -} from './SignalBase.js' - -export { SignalCompat } - -export default isCompatEnv() ? SignalCompat : Signal diff --git a/packages/teamplay/orm/Signal.d.ts b/packages/teamplay/orm/Signal.ts similarity index 75% rename from packages/teamplay/orm/Signal.d.ts rename to packages/teamplay/orm/Signal.ts index 3021ead..9222cfa 100644 --- a/packages/teamplay/orm/Signal.d.ts +++ b/packages/teamplay/orm/Signal.ts @@ -1,8 +1,8 @@ -export const SEGMENTS: unique symbol -export const ARRAY_METHOD: unique symbol -export const GET: unique symbol -export const GETTERS: unique symbol -export const DEFAULT_GETTERS: readonly string[] +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck +import { Signal } from './SignalBase.ts' +import SignalCompat from './Compat/SignalCompat.js' +import { isCompatEnv } from './compatEnv.js' export type PathSegment = string | number export type SignalPath = readonly PathSegment[] @@ -278,58 +278,23 @@ export type CollectionSignalFromSpec = ? CollectionSignal> : CollectionSignal -export interface Signal { - (...args: never[]): unknown - readonly __valueType?: TValue - readonly [SEGMENTS]: PathSegment[] -} - -export class Signal extends Function { - static ID_FIELDS: readonly string[] - static associations: readonly unknown[] - static addAssociation (association: object): void - static [GETTERS]: readonly string[] - - constructor (segments: PathSegment[]) - - path (): string - leaf (): string - parent (levels?: number): AnySignal - id (): string - batch(fn?: () => TResult): TResult | undefined - get (): TValue - getIds (): Array - peek (): TValue - getId (): string | number - getCollection (): string - getAssociations (): readonly unknown[] - map(callback: (value: AnySignal, index: number, array: AnySignal[]) => TResult): TResult[] - reduce( - callback: (previousValue: TResult, currentValue: AnySignal, currentIndex: number, array: AnySignal[]) => TResult, - initialValue: TResult - ): TResult - find (predicate: (value: AnySignal, index: number, obj: AnySignal[]) => unknown): AnySignal | undefined - set (value: TValue): Promise - assign (value: NonNullable extends object ? Partial> : never): Promise - push (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise - pop (): Promise extends ReadonlyArray ? Item | undefined : unknown> - unshift (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise - shift (): Promise extends ReadonlyArray ? Item | undefined : unknown> - insert (index: number, values: NonNullable extends ReadonlyArray ? Item | Item[] : unknown): Promise - remove (index: number, howMany?: number): Promise - move (from: number, to: number, howMany?: number): Promise - stringInsert (index: number, text: string): Promise - stringRemove (index: number, howMany?: number): Promise - increment (value?: number): Promise - add (value: unknown): Promise - del (): Promise -} - -export const regularBindings: ProxyHandler -export const extremelyLateBindings: ProxyHandler -export function isPublicCollectionSignal ($signal: unknown): boolean -export function isPublicDocumentSignal ($signal: unknown): boolean -export function isPublicCollection (collectionName: unknown): boolean -export function isPrivateCollection (collectionName: unknown): boolean - -export { Signal as default } +export { + Signal, + SEGMENTS, + ARRAY_METHOD, + GET, + GETTERS, + DEFAULT_GETTERS, + regularBindings, + extremelyLateBindings, + isPublicCollectionSignal, + isPublicDocumentSignal, + isPublicCollection, + isPrivateCollection +} from './SignalBase.ts' + +export { SignalCompat } + +const DefaultSignal = (isCompatEnv() ? SignalCompat : Signal) as typeof Signal + +export default DefaultSignal diff --git a/packages/teamplay/orm/SignalBase.js b/packages/teamplay/orm/SignalBase.ts similarity index 83% rename from packages/teamplay/orm/SignalBase.js rename to packages/teamplay/orm/SignalBase.ts index 7c70d1e..38c033d 100644 --- a/packages/teamplay/orm/SignalBase.js +++ b/packages/teamplay/orm/SignalBase.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/unbound-method */ +// @ts-nocheck /** * Implementation of the BaseSignal class which is used as a base class for all signals * and can be extended to create custom models for a particular path pattern of the data tree. @@ -29,12 +31,12 @@ import { stringInsertPublic as _stringInsertPublic, stringRemovePublic as _stringRemovePublic } from './dataTree.js' -import getSignal, { rawSignal } from './getSignal.js' +import getSignal, { rawSignal } from './getSignal.ts' import { docSubscriptions } from './Doc.js' import { IS_QUERY, HASH, QUERIES } from './Query.js' import { AGGREGATIONS, IS_AGGREGATION, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js' -import { ROOT_FUNCTION, ROOT_ID, getRoot } from './Root.js' -import { isPrivateMutationForbidden } from './connection.js' +import { ROOT_FUNCTION, ROOT_ID, getRoot } from './Root.ts' +import { isPrivateMutationForbidden } from './connection.ts' import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, @@ -71,12 +73,47 @@ export const GET = Symbol('get the value of the signal - either observed or raw' export const GETTERS = Symbol('get the list of this signal\'s getters') export const DEFAULT_GETTERS = ['path', 'id', 'get', 'peek', 'getId', 'map', 'reduce', 'find', 'getIds', 'getExtra', 'getCollection'] -export class Signal extends Function { +export interface Signal { + readonly [SEGMENTS]: Array + path: () => string + leaf: () => string + parent: (levels?: number) => Signal + id: () => string + batch: (fn?: () => TResult) => TResult | undefined + get: () => TValue + getIds: () => Array + peek: () => TValue + getId: () => string | number + getCollection: () => string + getAssociations: () => readonly unknown[] + map: (callback: (value: Signal, index: number, array: Signal[]) => TResult) => TResult[] + reduce: ( + callback: (previousValue: TResult, currentValue: Signal, currentIndex: number, array: Signal[]) => TResult, + initialValue: TResult + ) => TResult + find: (predicate: (value: Signal, index: number, obj: Signal[]) => unknown) => Signal | undefined + set: (value: TValue) => Promise + assign: (value: NonNullable extends object ? Partial> : never) => Promise + push: (value: NonNullable extends ReadonlyArray ? Item : unknown) => Promise + pop: () => Promise extends ReadonlyArray ? Item | undefined : unknown> + unshift: (value: NonNullable extends ReadonlyArray ? Item : unknown) => Promise + shift: () => Promise extends ReadonlyArray ? Item | undefined : unknown> + insert: (index: number, values: NonNullable extends ReadonlyArray ? Item | Item[] : unknown) => Promise + remove: (index: number, howMany?: number) => Promise + move: (from: number, to: number, howMany?: number) => Promise + stringInsert: (index: number, text: string) => Promise + stringRemove: (index: number, howMany?: number) => Promise + increment: (value?: number) => Promise + add: (value: unknown) => Promise + del: () => Promise +} + +export class Signal extends Function { static ID_FIELDS = DEFAULT_ID_FIELDS static [GETTERS] = DEFAULT_GETTERS static associations = [] - static addAssociation (association) { + static addAssociation (association: object): void { if (!association || typeof association !== 'object') { throw Error('Signal.addAssociation() expects an association object') } @@ -87,25 +124,25 @@ export class Signal extends Function { this.associations = own.concat(association) } - constructor (segments) { + constructor (segments: Array) { if (!Array.isArray(segments)) throw Error('Signal constructor expects an array of segments') super() this[SEGMENTS] = segments } - path () { + path (): string { if (arguments.length > 0) throw Error('Signal.path() does not accept any arguments') return this[SEGMENTS].join('.') } - leaf () { + leaf (): string { if (arguments.length > 0) throw Error('Signal.leaf() does not accept any arguments') const segments = this[SEGMENTS] if (segments.length === 0) return '' return String(segments[segments.length - 1]) } - parent (levels = 1) { + parent (levels = 1): Signal { if (arguments.length > 1) throw Error('Signal.parent() expects a single argument') if (arguments.length === 0) levels = 1 if (typeof levels !== 'number' || !Number.isFinite(levels) || !Number.isInteger(levels)) { @@ -124,18 +161,18 @@ export class Signal extends Function { return $cursor } - id () { + id (): string { return uuid() } - batch (fn) { + batch(fn?: () => TResult): TResult | undefined { if (arguments.length > 1) throw Error('Signal.batch() expects a single argument') if (fn == null) return if (typeof fn !== 'function') throw Error('Signal.batch() expects a function argument') return runInBatch(fn) } - [GET] (method) { + [GET] (method: (segments: Array) => TValue): TValue { if (arguments.length > 1) throw Error('Signal[GET]() only accepts method as an argument') if (this[SEGMENTS].length === 0) { const $root = getRoot(this) || this @@ -152,7 +189,7 @@ export class Signal extends Function { return method(getStorageSegmentsForSignal(this)) } - get () { + get (): TValue { if (arguments.length > 0) throw Error('Signal.get() does not accept any arguments') if (this[SEGMENTS].length === 3 && this[SEGMENTS][0] === QUERIES && this[SEGMENTS][2] === 'ids') { // TODO: This should never happen, but in reality it happens sometimes @@ -170,7 +207,7 @@ export class Signal extends Function { return this[GET](_get) } - getIds () { + getIds (): Array { if (arguments.length > 0) throw Error('Signal.getIds() does not accept any arguments') if (this[IS_QUERY]) { const $root = getRoot(this) || this @@ -196,12 +233,12 @@ export class Signal extends Function { } } - peek () { + peek (): TValue { if (arguments.length > 0) throw Error('Signal.peek() does not accept any arguments') return this[GET](getRaw) } - getId () { + getId (): string | number { if (this[SEGMENTS].length === 0) throw Error('Can\'t get the id of the root signal') if (this[SEGMENTS].length === 1) throw Error('Can\'t get the id of a collection') if (this[SEGMENTS][0] === AGGREGATIONS && this[SEGMENTS].length === 3) { @@ -213,7 +250,7 @@ export class Signal extends Function { return this[SEGMENTS][this[SEGMENTS].length - 1] } - getCollection () { + getCollection (): string { if (this[SEGMENTS].length === 0) throw Error('Can\'t get the collection of the root signal') if (this[SEGMENTS][0] === AGGREGATIONS) { return getAggregationCollectionName(this[SEGMENTS]) @@ -228,12 +265,12 @@ export class Signal extends Function { return this[SEGMENTS][0] } - getAssociations () { + getAssociations (): readonly unknown[] { const $raw = rawSignal(this) || this return $raw.constructor.associations || [] } - * [Symbol.iterator] () { + * [Symbol.iterator] (): IterableIterator { if (this[IS_QUERY]) { const $root = getRoot(this) || this const ids = getPrivateData($root?.[ROOT_ID], [QUERIES, this[HASH], 'ids']) @@ -253,7 +290,7 @@ export class Signal extends Function { } } - [ARRAY_METHOD] (method, nonArrayReturnValue, ...args) { + [ARRAY_METHOD] (method: string, nonArrayReturnValue: unknown, ...args: unknown[]): unknown { if (this[IS_QUERY]) { const collection = this[SEGMENTS][0] const $root = getRoot(this) || this @@ -277,19 +314,25 @@ export class Signal extends Function { )[method](...args) } - map (...args) { + map(callback: (value: Signal, index: number, array: Signal[]) => TResult): TResult[] { + const args = [callback] return this[ARRAY_METHOD]('map', [], ...args) } - reduce (...args) { + reduce( + callback: (previousValue: TResult, currentValue: Signal, currentIndex: number, array: Signal[]) => TResult, + initialValue: TResult + ): TResult { + const args = [callback, initialValue] return this[ARRAY_METHOD]('reduce', undefined, ...args) } - find (...args) { + find (predicate: (value: Signal, index: number, obj: Signal[]) => unknown): Signal | undefined { + const args = [predicate] return this[ARRAY_METHOD]('find', undefined, ...args) } - async set (value) { + async set (value: TValue): Promise { if (arguments.length > 1) throw Error('Signal.set() expects a single argument') if (this[SEGMENTS].length === 0) throw Error('Can\'t set the root signal data') const idFields = getIdFieldsForSegments(this[SEGMENTS]) @@ -305,7 +348,7 @@ export class Signal extends Function { } } - async assign (value) { + async assign (value: NonNullable extends object ? Partial> : never): Promise { if (arguments.length > 1) throw Error('Signal.assign() expects a single argument') if (this[SEGMENTS].length === 0) throw Error('Can\'t assign to the root signal data') if (!value) return @@ -324,47 +367,47 @@ export class Signal extends Function { await Promise.all(promises) } - async push (value) { + async push (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise { if (arguments.length > 1) throw Error('Signal.push() expects a single argument') const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayPushPublic(segments, value) + if (isPublicCollection(segments[0])) return await _arrayPushPublic(segments, value) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayPushPrivateData(getOwningRootId(this), segments, value) } - async pop () { + async pop (): Promise extends ReadonlyArray ? Item | undefined : unknown> { if (arguments.length > 0) throw Error('Signal.pop() does not accept any arguments') const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayPopPublic(segments) + if (isPublicCollection(segments[0])) return await _arrayPopPublic(segments) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayPopPrivateData(getOwningRootId(this), segments) } - async unshift (value) { + async unshift (value: NonNullable extends ReadonlyArray ? Item : unknown): Promise { if (arguments.length > 1) throw Error('Signal.unshift() expects a single argument') const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayUnshiftPublic(segments, value) + if (isPublicCollection(segments[0])) return await _arrayUnshiftPublic(segments, value) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayUnshiftPrivateData(getOwningRootId(this), segments, value) } - async shift () { + async shift (): Promise extends ReadonlyArray ? Item | undefined : unknown> { if (arguments.length > 0) throw Error('Signal.shift() does not accept any arguments') const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayShiftPublic(segments) + if (isPublicCollection(segments[0])) return await _arrayShiftPublic(segments) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayShiftPrivateData(getOwningRootId(this), segments) } - async insert (index, values) { + async insert (index: number, values: NonNullable extends ReadonlyArray ? Item | Item[] : unknown): Promise { if (arguments.length < 2) throw Error('Not enough arguments for insert') if (arguments.length > 2) throw Error('Signal.insert() expects two arguments') if (typeof index !== 'number' || !Number.isFinite(index)) { @@ -373,12 +416,12 @@ export class Signal extends Function { const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayInsertPublic(segments, index, values) + if (isPublicCollection(segments[0])) return await _arrayInsertPublic(segments, index, values) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayInsertPrivateData(getOwningRootId(this), segments, index, values) } - async remove (index, howMany = 1) { + async remove (index: number, howMany = 1): Promise { if (arguments.length < 1) throw Error('Not enough arguments for remove') if (arguments.length > 2) throw Error('Signal.remove() expects one or two arguments') if (typeof index !== 'number' || !Number.isFinite(index)) { @@ -387,12 +430,12 @@ export class Signal extends Function { const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayRemovePublic(segments, index, howMany) + if (isPublicCollection(segments[0])) return await _arrayRemovePublic(segments, index, howMany) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayRemovePrivateData(getOwningRootId(this), segments, index, howMany) } - async move (from, to, howMany = 1) { + async move (from: number, to: number, howMany = 1): Promise { if (arguments.length < 2) throw Error('Not enough arguments for move') if (arguments.length > 3) throw Error('Signal.move() expects two or three arguments') if (typeof from !== 'number' || !Number.isFinite(from) || typeof to !== 'number' || !Number.isFinite(to)) { @@ -401,12 +444,12 @@ export class Signal extends Function { const segments = ensureArrayTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _arrayMovePublic(segments, from, to, howMany) + if (isPublicCollection(segments[0])) return await _arrayMovePublic(segments, from, to, howMany) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return arrayMovePrivateData(getOwningRootId(this), segments, from, to, howMany) } - async stringInsert (index, text) { + async stringInsert (index: number, text: string): Promise { if (arguments.length < 2) throw Error('Not enough arguments for stringInsert') if (arguments.length > 2) throw Error('Signal.stringInsert() expects two arguments') if (typeof index !== 'number' || !Number.isFinite(index)) { @@ -415,12 +458,12 @@ export class Signal extends Function { const segments = ensureValueTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _stringInsertPublic(segments, index, text) + if (isPublicCollection(segments[0])) return await _stringInsertPublic(segments, index, text) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return stringInsertPrivateData(getOwningRootId(this), segments, index, text) } - async stringRemove (index, howMany = 1) { + async stringRemove (index: number, howMany = 1): Promise { if (arguments.length < 2) throw Error('Not enough arguments for stringRemove') if (arguments.length > 2) throw Error('Signal.stringRemove() expects two arguments') if (typeof index !== 'number' || !Number.isFinite(index)) { @@ -429,12 +472,12 @@ export class Signal extends Function { const segments = ensureValueTarget(this) const idFields = getIdFieldsForSegments(segments) if (isIdFieldPath(segments, idFields)) return - if (isPublicCollection(segments[0])) return _stringRemovePublic(segments, index, howMany) + if (isPublicCollection(segments[0])) return await _stringRemovePublic(segments, index, howMany) if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly) return stringRemovePrivateData(getOwningRootId(this), segments, index, howMany) } - async increment (value) { + async increment (value?: number): Promise { if (arguments.length > 1) throw Error('Signal.increment() expects a single argument') if (value === undefined) value = 1 if (typeof value !== 'number') throw Error('Signal.increment() expects a number argument') @@ -454,7 +497,7 @@ export class Signal extends Function { return currentValue + value } - async add (value) { + async add (value: unknown): Promise { if (arguments.length > 1) throw Error('Signal.add() expects a single argument') const id = resolveAddDocId(value, uuid) const idFields = getIdFieldsForSegments([this[SEGMENTS][0], id]) @@ -462,7 +505,7 @@ export class Signal extends Function { return id } - async del () { + async del (): Promise { if (arguments.length > 0) throw Error('Signal.del() does not accept any arguments') if (this[SEGMENTS].length === 0) throw Error('Can\'t delete the root signal data') const idFields = getIdFieldsForSegments(this[SEGMENTS]) @@ -603,7 +646,7 @@ export const extremelyLateBindings = { throw Error('Signal.stop() expects targetPath to be a string') } const absolutePath = joinScopePath($parent.path(), relativePath || '') - return compatStopOnRoot(getRoot($parent) || $parent, absolutePath) + compatStopOnRoot(getRoot($parent) || $parent, absolutePath); return } } diff --git a/packages/teamplay/orm/Value.js b/packages/teamplay/orm/Value.js index 6520c75..9563b3f 100644 --- a/packages/teamplay/orm/Value.js +++ b/packages/teamplay/orm/Value.js @@ -1,5 +1,5 @@ -import { SEGMENTS } from './Signal.js' -import { getRoot, ROOT_ID } from './Root.js' +import { SEGMENTS } from './Signal.ts' +import { getRoot, ROOT_ID } from './Root.ts' import { delPrivateData, setPrivateData } from './privateData.js' import FinalizationRegistry from '../utils/MockFinalizationRegistry.js' diff --git a/packages/teamplay/orm/addModel.d.ts b/packages/teamplay/orm/addModel.d.ts deleted file mode 100644 index 5758296..0000000 --- a/packages/teamplay/orm/addModel.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { SignalClass } from './Signal.js' - -export const MODELS: Record> - -export default function addModel> ( - pattern: string, - Model: TModel -): void - -export function findModel (segments: ReadonlyArray): SignalClass | undefined diff --git a/packages/teamplay/orm/addModel.js b/packages/teamplay/orm/addModel.ts similarity index 94% rename from packages/teamplay/orm/addModel.js rename to packages/teamplay/orm/addModel.ts index d892b82..0c8d26f 100644 --- a/packages/teamplay/orm/addModel.js +++ b/packages/teamplay/orm/addModel.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck export const MODELS = {} export default function addModel (pattern, Model) { diff --git a/packages/teamplay/orm/connection.d.ts b/packages/teamplay/orm/connection.d.ts deleted file mode 100644 index 0ee5138..0000000 --- a/packages/teamplay/orm/connection.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function connection (...args: any[]): any -export function setConnection (value: any): void -export function getConnection (): any -export function getDefaultFetchOnly (): boolean -export function setDefaultFetchOnly (value?: boolean): boolean -export function fetchOnly (fn: () => T): T -export function setFetchOnly (value?: boolean): boolean -export function publicOnly (fn: () => T): T -export function setPublicOnly (value?: boolean): boolean diff --git a/packages/teamplay/orm/connection.js b/packages/teamplay/orm/connection.ts similarity index 93% rename from packages/teamplay/orm/connection.js rename to packages/teamplay/orm/connection.ts index 4611f26..f194905 100644 --- a/packages/teamplay/orm/connection.js +++ b/packages/teamplay/orm/connection.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck import { isCompatEnv } from './compatEnv.js' export let connection diff --git a/packages/teamplay/orm/dataTree.js b/packages/teamplay/orm/dataTree.js index 55bff82..d26279c 100644 --- a/packages/teamplay/orm/dataTree.js +++ b/packages/teamplay/orm/dataTree.js @@ -1,7 +1,7 @@ import { observable, raw } from '@nx-js/observer-util' import jsonDiff from 'json0-ot-diff' import diffMatchPatch from 'diff-match-patch' -import { getConnection } from './connection.js' +import { getConnection } from './connection.ts' import setDiffDeep from '../utils/setDiffDeep.js' import { getIdFieldsForSegments, injectIdFields, stripIdFields, isPlainObject, isIdFieldPath } from './idFields.js' import { emitModelChange, isModelEventsEnabled } from './Compat/modelEvents.js' diff --git a/packages/teamplay/orm/disposeRootContext.js b/packages/teamplay/orm/disposeRootContext.js index 2d72889..5871837 100644 --- a/packages/teamplay/orm/disposeRootContext.js +++ b/packages/teamplay/orm/disposeRootContext.js @@ -1,6 +1,6 @@ import { aggregationSubscriptions } from './Aggregation.js' import { docSubscriptions } from './Doc.js' -import { purgeSignalHashes } from './getSignal.js' +import { purgeSignalHashes } from './getSignal.ts' import { querySubscriptions } from './Query.js' import { deleteRootContext, diff --git a/packages/teamplay/orm/getSignal.d.ts b/packages/teamplay/orm/getSignal.d.ts deleted file mode 100644 index 4a34796..0000000 --- a/packages/teamplay/orm/getSignal.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { AnySignal, SignalClass, SignalPath } from './Signal.js' - -export default function getSignal ( - $root?: AnySignal, - segments?: SignalPath, - options?: { - useExtremelyLateBindings?: boolean - rootId?: string - signalHash?: string - proxyHandlers?: ProxyHandler - } -): AnySignal - -export function getSignalClass (segments: SignalPath, rootId?: string): SignalClass -export function rawSignal (proxy: TSignal): TSignal | undefined -// eslint-disable-next-line @typescript-eslint/naming-convention -export const __DEBUG_SIGNALS_CACHE__: { - readonly size: number - get: (key: string) => unknown - set: (key: string, value: unknown, dependencies?: unknown[]) => void - delete: (key: string) => void -} -export function purgeSignalHashes (hashes: Iterable): void diff --git a/packages/teamplay/orm/getSignal.js b/packages/teamplay/orm/getSignal.ts similarity index 94% rename from packages/teamplay/orm/getSignal.js rename to packages/teamplay/orm/getSignal.ts index 666be52..64c64b0 100644 --- a/packages/teamplay/orm/getSignal.js +++ b/packages/teamplay/orm/getSignal.ts @@ -1,12 +1,14 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck import Cache from './Cache.js' -import Signal, { SEGMENTS, regularBindings, extremelyLateBindings, isPublicCollection, isPrivateCollection } from './Signal.js' -import { findModel } from './addModel.js' +import Signal, { SEGMENTS, regularBindings, extremelyLateBindings, isPublicCollection, isPrivateCollection } from './Signal.ts' +import { findModel } from './addModel.ts' import { LOCAL } from './$.js' -import { ROOT, ROOT_ID, GLOBAL_ROOT_ID } from './Root.js' +import { ROOT, ROOT_ID, GLOBAL_ROOT_ID } from './Root.ts' import { QUERIES } from './Query.js' import { AGGREGATIONS } from './Aggregation.js' import { isCompatEnv } from './compatEnv.js' -import { getConnection } from './connection.js' +import { getConnection } from './connection.ts' import { resolveRefSegmentsSafe } from './Compat/refFallback.js' import { getSignalIdentityHash } from './rootScope.js' import { isRootContextClosed, registerRootOwnedSignalHash } from './rootContext.js' diff --git a/packages/teamplay/orm/idFields.js b/packages/teamplay/orm/idFields.js index f1621bc..a45ae99 100644 --- a/packages/teamplay/orm/idFields.js +++ b/packages/teamplay/orm/idFields.js @@ -1,4 +1,4 @@ -import { findModel } from './addModel.js' +import { findModel } from './addModel.ts' export const DEFAULT_ID_FIELDS = ['_id'] diff --git a/packages/teamplay/orm/index.d.ts b/packages/teamplay/orm/index.d.ts deleted file mode 100644 index a0c0e05..0000000 --- a/packages/teamplay/orm/index.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Signal } from './Signal.js' - -export const BaseModel: typeof Signal -export default BaseModel -export { default as Signal } from './Signal.js' -export type { - AggregationSignal, - AnySignal, - CollectionSignal, - CollectionSpec, - DocumentSignal, - FromJsonSchema, - JsonSchema, - JsonSchemaSpec, - SignalClass, - TypedSignal, - ZodLikeSchema, - ZodSchemaSpec -} from './Signal.js' -export type { RootSignal } from '../index.js' - -export function belongsTo (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export function hasMany (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any -export function hasOne (AssociatedOrmEntity: any, options?: Record): (OrmEntity: any) => any diff --git a/packages/teamplay/orm/index.js b/packages/teamplay/orm/index.js deleted file mode 100644 index 0c6d568..0000000 --- a/packages/teamplay/orm/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import Signal from './Signal.js' -export { belongsTo, hasMany, hasOne } from './associations.js' - -export const BaseModel = Signal -export default BaseModel diff --git a/packages/teamplay/orm/index.ts b/packages/teamplay/orm/index.ts new file mode 100644 index 0000000..9412371 --- /dev/null +++ b/packages/teamplay/orm/index.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck +import Signal from './Signal.ts' +export { belongsTo, hasMany, hasOne } from './associations.js' +export type { + AggregationSignal, + CollectionSignal, + CollectionSignalFromSpec, + CollectionSpec, + DocumentSignal, + FromJsonSchema, + JsonSchema, + JsonSchemaSpec, + QuerySignal, + SignalClass, + TypedSignal, + ZodLikeSchema, + ZodSchemaSpec +} from './Signal.ts' +export type { RootSignal, TeamplayCollections } from '../index.ts' + +export const BaseModel = Signal +export default BaseModel diff --git a/packages/teamplay/orm/rootContext.js b/packages/teamplay/orm/rootContext.js index 336e738..f44aa4e 100644 --- a/packages/teamplay/orm/rootContext.js +++ b/packages/teamplay/orm/rootContext.js @@ -1,6 +1,6 @@ import { observable } from '@nx-js/observer-util' import { normalizeRootId } from './rootScope.js' -import { getDefaultFetchOnly } from './connection.js' +import { getDefaultFetchOnly } from './connection.ts' const ROOT_CONTEXTS = new Map() const CLOSED_ROOT_CONTEXTS = new Set() diff --git a/packages/teamplay/orm/rootScope.js b/packages/teamplay/orm/rootScope.js index bc02182..10a964a 100644 --- a/packages/teamplay/orm/rootScope.js +++ b/packages/teamplay/orm/rootScope.js @@ -1,4 +1,4 @@ -import { GLOBAL_ROOT_ID } from './Root.js' +import { GLOBAL_ROOT_ID } from './Root.ts' const REGEX_PRIVATE_COLLECTION = /^[_$]/ diff --git a/packages/teamplay/orm/sub.d.ts b/packages/teamplay/orm/sub.d.ts deleted file mode 100644 index 5694b1b..0000000 --- a/packages/teamplay/orm/sub.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - AggregationSignal, - CollectionDocument, - CollectionDocumentModel, - CollectionSignal, - QuerySignal, - Signal -} from './Signal.js' -import type { TeamplayCollections } from '../index.js' - -export default function sub> ( - $signal: TSignal -): TSignal | Promise - -export default function sub any> ( - $collection: CollectionSignal, - params: Record -): QuerySignal | Promise> - -export default function sub ( - $aggregation: { - readonly __isAggregation: true - readonly collection: TCollection - }, - params?: Record -): AggregationSignal< -CollectionDocument, -CollectionDocumentModel -> | Promise, -CollectionDocumentModel ->> - -export default function sub any> ( - $aggregation: { - readonly __isAggregation: true - readonly collection: string - readonly __teamplayDocument?: TDocument - readonly __teamplayDocumentModel?: TDocumentModel - }, - params?: Record -): AggregationSignal | Promise> - -export default function sub ( - $signal: TSignal, - params?: TParams -): any diff --git a/packages/teamplay/orm/sub.js b/packages/teamplay/orm/sub.ts similarity index 74% rename from packages/teamplay/orm/sub.js rename to packages/teamplay/orm/sub.ts index d83485c..28d4be4 100644 --- a/packages/teamplay/orm/sub.js +++ b/packages/teamplay/orm/sub.ts @@ -1,10 +1,58 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-misused-promises, @typescript-eslint/promise-function-async, no-async-promise-executor, @typescript-eslint/restrict-template-expressions */ +// @ts-nocheck import { isAggregationHeader, isAggregationFunction, isClientAggregationFunction } from '@teamplay/utils/aggregation' -import Signal, { SEGMENTS, isPublicCollectionSignal, isPublicDocumentSignal } from './Signal.js' +import Signal, { SEGMENTS, isPublicCollectionSignal, isPublicDocumentSignal } from './Signal.ts' import { docSubscriptions } from './Doc.js' import { querySubscriptions, getQuerySignal } from './Query.js' import { aggregationSubscriptions, getAggregationSignal } from './Aggregation.js' -import { getRoot } from './Root.js' +import { getRoot } from './Root.ts' import isServer from '../utils/isServer.js' +import type { + AggregationSignal, + CollectionDocument, + CollectionDocumentModel, + CollectionSignal, + QuerySignal +} from './Signal.ts' +import type { TeamplayCollections } from '../index.ts' + +export default function sub> ( + $signal: TSignal +): TSignal | Promise + +export default function sub any> ( + $collection: CollectionSignal, + params: Record +): QuerySignal | Promise> + +export default function sub ( + $aggregation: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> | Promise, +CollectionDocumentModel +>> + +export default function sub any> ( + $aggregation: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record +): AggregationSignal | Promise> + +export default function sub ( + $signal: TSignal, + params?: TParams +): any export default function sub ($signal, params) { // TODO: temporarily disable support for multiple subscriptions @@ -40,7 +88,7 @@ export default function sub ($signal, params) { throw Error(ERRORS.gotAggregationFunction($signal)) } } else if (typeof $signal === 'function' && !($signal instanceof Signal)) { - return api$($signal, params) + api$($signal, params) } else { throw Error('Invalid args passed for sub()') } @@ -64,7 +112,7 @@ function getAggregationFromFunction (fn, collection, params) { function doc$ ($doc) { const promise = docSubscriptions.subscribe($doc) if (!promise) return $doc - return new Promise(resolve => promise.then(() => resolve($doc))) + return new Promise(resolve => promise.then(() => { resolve($doc) })) } function query$ ($collection, params) { @@ -75,14 +123,14 @@ function query$ ($collection, params) { const $query = getQuerySignal(collectionName, params, signalOptions) const promise = querySubscriptions.subscribe($query) if (!promise) return $query - return new Promise(resolve => promise.then(() => resolve($query))) + return new Promise(resolve => promise.then(() => { resolve($query) })) } function aggregation$ (collectionName, params, signalOptions) { const $aggregationQuery = getAggregationSignal(collectionName, params, signalOptions) const promise = aggregationSubscriptions.subscribe($aggregationQuery) if (!promise) return $aggregationQuery - return new Promise(resolve => promise.then(() => resolve($aggregationQuery))) + return new Promise(resolve => promise.then(() => { resolve($aggregationQuery) })) } function api$ (fn, args) { diff --git a/packages/teamplay/package.json b/packages/teamplay/package.json index f121e55..95c9297 100644 --- a/packages/teamplay/package.json +++ b/packages/teamplay/package.json @@ -3,10 +3,17 @@ "version": "0.4.0-alpha.100", "description": "Full-stack signals ORM with multiplayer", "type": "module", - "main": "index.js", + "main": "index.ts", + "types": "index.ts", "exports": { - ".": "./index.js", - "./orm": "./orm/index.js", + ".": { + "types": "./index.ts", + "default": "./index.ts" + }, + "./orm": { + "types": "./orm/index.ts", + "default": "./orm/index.ts" + }, "./connect": "./connect/index.js", "./server": "./server.js", "./connect-test": "./connect/test.js", @@ -75,7 +82,12 @@ } }, "jest": { - "transform": {}, + "transform": { + "^.+\\.ts$": "./test/ts-transform.cjs" + }, + "extensionsToTreatAsEsm": [ + ".ts" + ], "testEnvironment": "jsdom", "testRegex": "test_client/.*\\.jsx?$", "testPathIgnorePatterns": [ diff --git a/packages/teamplay/react/convertToObserver.js b/packages/teamplay/react/convertToObserver.js index e0319f9..a1b5cde 100644 --- a/packages/teamplay/react/convertToObserver.js +++ b/packages/teamplay/react/convertToObserver.js @@ -4,7 +4,7 @@ import _throttle from 'lodash/throttle.js' import { createCaches, getDummyCache } from '@teamplay/cache' import { __increment, __decrement } from '@teamplay/debug' import executionContextTracker from './executionContextTracker.js' -import { pipeComponentMeta, useUnmount, useId, useTriggerUpdate } from './helpers.js' +import { pipeComponentMeta, useUnmount, useId, useTriggerUpdate } from './helpers.ts' import trapRender from './trapRender.js' import { scheduleReaction } from '../orm/batchScheduler.js' import { isCompatComponent, unmarkCompatComponent } from './compatComponentRegistry.js' diff --git a/packages/teamplay/react/helpers.d.ts b/packages/teamplay/react/helpers.d.ts deleted file mode 100644 index 7a353f0..0000000 --- a/packages/teamplay/react/helpers.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function useId (): string -export function useNow (interval?: number): number -export function useScheduleUpdate (): (delay?: number) => void -export function useTriggerUpdate (): () => void -export type EffectCleanup = () => void -export type EffectCallback = () => undefined | EffectCleanup - -export function useDidUpdate (fn: EffectCallback, deps?: any[]): void -export function useOnce (condition: any, fn: EffectCallback): void -export function useSyncEffect (fn: EffectCallback, deps?: any[]): void diff --git a/packages/teamplay/react/helpers.js b/packages/teamplay/react/helpers.ts similarity index 98% rename from packages/teamplay/react/helpers.js rename to packages/teamplay/react/helpers.ts index 707441b..4c974e0 100644 --- a/packages/teamplay/react/helpers.js +++ b/packages/teamplay/react/helpers.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck import { useContext, createContext, useRef, useEffect, useLayoutEffect } from 'react' export const ComponentMetaContext = createContext({}) diff --git a/packages/teamplay/react/universal$.js b/packages/teamplay/react/universal$.js index 7df0a5a..1597f9a 100644 --- a/packages/teamplay/react/universal$.js +++ b/packages/teamplay/react/universal$.js @@ -1,5 +1,5 @@ import $ from '../orm/$.js' -import { useCache } from './helpers.js' +import { useCache } from './helpers.ts' import executionContextTracker from './executionContextTracker.js' // universal versions of $() which work as a plain function or as a react hook diff --git a/packages/teamplay/react/universalSub.js b/packages/teamplay/react/universalSub.js index 7301b53..bb1fa95 100644 --- a/packages/teamplay/react/universalSub.js +++ b/packages/teamplay/react/universalSub.js @@ -3,7 +3,7 @@ // Having the same sub() function working with either await or without it // is confusing. It's better to have a separate function for the hook. import { useRef } from 'react' -import sub from '../orm/sub.js' +import sub from '../orm/sub.ts' import executionContextTracker from './executionContextTracker.js' // universal versions of sub() which work as a plain function or as a react hook diff --git a/packages/teamplay/react/useSub.d.ts b/packages/teamplay/react/useSub.d.ts deleted file mode 100644 index 6a81c7c..0000000 --- a/packages/teamplay/react/useSub.d.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { - AggregationSignal, - CollectionDocument, - CollectionDocumentModel, - CollectionSignal, - QuerySignal, - Signal -} from '../orm/Signal.js' -import type { TeamplayCollections } from '../index.js' - -export interface UseSubOptions { - async?: boolean - defer?: boolean | number - batch?: boolean - compatAttemptCleanup?: boolean -} - -export default function useSub> ( - signal: TSignal, - params?: undefined, - options?: UseSubOptions -): TSignal - -export default function useSub any> ( - signal: CollectionSignal, - params: Record, - options?: UseSubOptions -): QuerySignal - -export default function useSub ( - signal: { - readonly __isAggregation: true - readonly collection: TCollection - }, - params?: Record, - options?: UseSubOptions -): AggregationSignal< -CollectionDocument, -CollectionDocumentModel -> - -export default function useSub any> ( - signal: { - readonly __isAggregation: true - readonly collection: string - readonly __teamplayDocument?: TDocument - readonly __teamplayDocumentModel?: TDocumentModel - }, - params?: Record, - options?: UseSubOptions -): AggregationSignal - -export default function useSub (signal: any, params?: any, options?: UseSubOptions): any - -export function useAsyncSub> ( - signal: TSignal, - params?: undefined, - options?: UseSubOptions -): TSignal - -export function useAsyncSub any> ( - signal: CollectionSignal, - params: Record, - options?: UseSubOptions -): QuerySignal - -export function useAsyncSub ( - signal: { - readonly __isAggregation: true - readonly collection: TCollection - }, - params?: Record, - options?: UseSubOptions -): AggregationSignal< -CollectionDocument, -CollectionDocumentModel -> - -export function useAsyncSub any> ( - signal: { - readonly __isAggregation: true - readonly collection: string - readonly __teamplayDocument?: TDocument - readonly __teamplayDocumentModel?: TDocumentModel - }, - params?: Record, - options?: UseSubOptions -): AggregationSignal - -export function useAsyncSub (signal: any, params?: any, options?: UseSubOptions): any -export function setUseDeferredValue (enabled: boolean): void -export function setDefaultDefer (value?: boolean | number): boolean | number | undefined diff --git a/packages/teamplay/react/useSub.js b/packages/teamplay/react/useSub.ts similarity index 70% rename from packages/teamplay/react/useSub.js rename to packages/teamplay/react/useSub.ts index 71d4d7d..8a662d2 100644 --- a/packages/teamplay/react/useSub.js +++ b/packages/teamplay/react/useSub.ts @@ -1,10 +1,28 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck import { useRef, useDeferredValue } from 'react' -import sub from '../orm/sub.js' -import { useScheduleUpdate, useCache, useDefer, useId } from './helpers.js' +import sub from '../orm/sub.ts' +import { useScheduleUpdate, useCache, useDefer, useId } from './helpers.ts' import executionContextTracker from './executionContextTracker.js' import * as promiseBatcher from './promiseBatcher.js' import renderAttemptDestroyer from './renderAttemptDestroyer.js' import { markCompatComponent } from './compatComponentRegistry.js' +import type { + AggregationSignal, + CollectionDocument, + CollectionDocumentModel, + CollectionSignal, + QuerySignal, + Signal +} from '../orm/Signal.ts' +import type { TeamplayCollections } from '../index.ts' + +export interface UseSubOptions { + async?: boolean + defer?: boolean | number + batch?: boolean + compatAttemptCleanup?: boolean +} let TEST_THROTTLING = false @@ -14,10 +32,82 @@ let USE_DEFERRED_VALUE = true // by default we want to defer stuff if possible instead of throwing promises let DEFAULT_DEFER = true +export function useAsyncSub> ( + signal: TSignal, + params?: undefined, + options?: UseSubOptions +): TSignal + +export function useAsyncSub any> ( + signal: CollectionSignal, + params: Record, + options?: UseSubOptions +): QuerySignal + +export function useAsyncSub ( + signal: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> + +export function useAsyncSub any> ( + signal: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal + +export function useAsyncSub (signal: any, params?: any, options?: UseSubOptions): any export function useAsyncSub (signal, params, options) { return useSub(signal, params, { ...options, async: true }) } +export default function useSub> ( + signal: TSignal, + params?: undefined, + options?: UseSubOptions +): TSignal + +export default function useSub any> ( + signal: CollectionSignal, + params: Record, + options?: UseSubOptions +): QuerySignal + +export default function useSub ( + signal: { + readonly __isAggregation: true + readonly collection: TCollection + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal< +CollectionDocument, +CollectionDocumentModel +> + +export default function useSub any> ( + signal: { + readonly __isAggregation: true + readonly collection: string + readonly __teamplayDocument?: TDocument + readonly __teamplayDocumentModel?: TDocumentModel + }, + params?: Record, + options?: UseSubOptions +): AggregationSignal + +export default function useSub (signal: any, params?: any, options?: UseSubOptions): any export default function useSub (signal, params, options) { if (USE_DEFERRED_VALUE) { return useSubDeferred(signal, params, options) // eslint-disable-line react-hooks/rules-of-hooks diff --git a/packages/teamplay/react/useSuspendMemo.js b/packages/teamplay/react/useSuspendMemo.js index 934f7ff..e732c4d 100644 --- a/packages/teamplay/react/useSuspendMemo.js +++ b/packages/teamplay/react/useSuspendMemo.js @@ -1,5 +1,5 @@ import executionContextTracker from './executionContextTracker.js' -import { useCache, useId } from './helpers.js' +import { useCache, useId } from './helpers.ts' import { markCompatComponent } from './compatComponentRegistry.js' import renderAttemptDestroyer from './renderAttemptDestroyer.js' diff --git a/packages/teamplay/react/wrapIntoSuspense.js b/packages/teamplay/react/wrapIntoSuspense.js index 42c66b8..7653696 100644 --- a/packages/teamplay/react/wrapIntoSuspense.js +++ b/packages/teamplay/react/wrapIntoSuspense.js @@ -1,7 +1,7 @@ // useSyncExternalStore is used to trigger an update same as in MobX // ref: https://github.com/mobxjs/mobx/blob/94bc4997c14152ff5aefcaac64d982d5c21ba51a/packages/mobx-react-lite/src/useObserver.ts import { useSyncExternalStore, forwardRef as _forwardRef, memo, createElement as el, Suspense, useId, useRef } from 'react' -import { pipeComponentMeta, pipeComponentDisplayName, ComponentMetaContext } from './helpers.js' +import { pipeComponentMeta, pipeComponentDisplayName, ComponentMetaContext } from './helpers.ts' // TODO: probably add FinalizationRegistry to handle destruction of observer() before it ever mounted. // In such case we might have a memory leak because subscribe() would never fire and would never diff --git a/packages/teamplay/server.js b/packages/teamplay/server.js index 463dc32..1517f25 100644 --- a/packages/teamplay/server.js +++ b/packages/teamplay/server.js @@ -1,5 +1,5 @@ import createChannel from '@teamplay/channel/server' -import { connection, setConnection, setDefaultFetchOnly, setPublicOnly } from './orm/connection.js' +import { connection, setConnection, setDefaultFetchOnly, setPublicOnly } from './orm/connection.ts' export { default as ShareDB } from 'sharedb' export { diff --git a/packages/teamplay/test/$.js b/packages/teamplay/test/$.js index ba5513a..2a6dce2 100644 --- a/packages/teamplay/test/$.js +++ b/packages/teamplay/test/$.js @@ -2,8 +2,8 @@ import React from 'react' import { it, describe, afterEach, before } from 'mocha' import { strict as assert } from 'node:assert' import { afterEachTestGc, runGc } from './_helpers.js' -import { $, batch, batchModel, clone, initLocalCollection, __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.js' -import { GLOBAL_ROOT_ID } from '../orm/Root.js' +import { $, batch, batchModel, clone, initLocalCollection, __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.ts' +import { GLOBAL_ROOT_ID } from '../orm/Root.ts' import { LOCAL } from '../orm/$.js' import { delPrivateData, getPrivateData } from '../orm/privateData.js' import { del as _del, set as _set } from '../orm/dataTree.js' diff --git a/packages/teamplay/test/_helpers.js b/packages/teamplay/test/_helpers.js index 87ce29d..8110f8e 100644 --- a/packages/teamplay/test/_helpers.js +++ b/packages/teamplay/test/_helpers.js @@ -1,6 +1,6 @@ import { before, beforeEach, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.js' +import { __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.ts' import { docSubscriptions } from '../orm/Doc.js' import { querySubscriptions } from '../orm/Query.js' import { getSubscriptionGcDelay, setSubscriptionGcDelay } from '../orm/subscriptionGcDelay.js' diff --git a/packages/teamplay/test/aggregationEvents.js b/packages/teamplay/test/aggregationEvents.js index e572826..8e98239 100644 --- a/packages/teamplay/test/aggregationEvents.js +++ b/packages/teamplay/test/aggregationEvents.js @@ -1,7 +1,7 @@ import { it, describe, before } from 'mocha' import { strict as assert } from 'node:assert' import { afterEachTestGc, runGc } from './_helpers.js' -import { $, sub, aggregation } from '../index.js' +import { $, sub, aggregation } from '../index.ts' import { aggregationSubscriptions } from '../orm/Aggregation.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test/constructorStaticAccess.js b/packages/teamplay/test/constructorStaticAccess.js index 662d4ee..9ebd90a 100644 --- a/packages/teamplay/test/constructorStaticAccess.js +++ b/packages/teamplay/test/constructorStaticAccess.js @@ -1,7 +1,7 @@ import { describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { $, addModel } from '../index.js' -import Signal from '../orm/Signal.js' +import { $, addModel } from '../index.ts' +import Signal from '../orm/Signal.ts' describe('Signal method this.constructor static access', () => { it('resolves constructor to model class inside method body', () => { diff --git a/packages/teamplay/test/dotSyntax.js b/packages/teamplay/test/dotSyntax.js index 4265b1e..c92f39c 100644 --- a/packages/teamplay/test/dotSyntax.js +++ b/packages/teamplay/test/dotSyntax.js @@ -1,7 +1,7 @@ import { it, describe, before } from 'mocha' import { strict as assert } from 'node:assert' import { runGc } from './_helpers.js' -import { $, signal, __DEBUG_SIGNALS_CACHE__ as signalsCache, GLOBAL_ROOT_ID } from '../index.js' +import { $, signal, __DEBUG_SIGNALS_CACHE__ as signalsCache, GLOBAL_ROOT_ID } from '../index.ts' import connect from '../connect/test.js' before(connect) diff --git a/packages/teamplay/test/gcCleanup.js b/packages/teamplay/test/gcCleanup.js index 4a9210b..cd05033 100644 --- a/packages/teamplay/test/gcCleanup.js +++ b/packages/teamplay/test/gcCleanup.js @@ -1,12 +1,12 @@ import { it, describe, before } from 'mocha' import { strict as assert } from 'node:assert' import { runGc } from './_helpers.js' -import { $, sub, aggregation, __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.js' -import { getConnection } from '../orm/connection.js' +import { $, sub, aggregation, __DEBUG_SIGNALS_CACHE__ as signalsCache } from '../index.ts' +import { getConnection } from '../orm/connection.ts' import { docSubscriptions } from '../orm/Doc.js' import { querySubscriptions } from '../orm/Query.js' import { aggregationSubscriptions } from '../orm/Aggregation.js' -import { getRoot, ROOT_ID } from '../orm/Root.js' +import { getRoot, ROOT_ID } from '../orm/Root.ts' import { getScopedSignalHash } from '../orm/rootScope.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test/getCollectionCompat.js b/packages/teamplay/test/getCollectionCompat.js index 9d9a796..487af87 100644 --- a/packages/teamplay/test/getCollectionCompat.js +++ b/packages/teamplay/test/getCollectionCompat.js @@ -1,7 +1,7 @@ import { describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { $, addModel } from '../index.js' -import Signal from '../orm/Signal.js' +import { $, addModel } from '../index.ts' +import Signal from '../orm/Signal.ts' describe('Signal.getCollection() compatibility', () => { it('prefers static collection over path collection for compat-mounted model', () => { diff --git a/packages/teamplay/test/idFields.js b/packages/teamplay/test/idFields.js index edbbef5..cf21aa2 100644 --- a/packages/teamplay/test/idFields.js +++ b/packages/teamplay/test/idFields.js @@ -1,7 +1,7 @@ import { it, describe, before, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { $, sub, aggregation } from '../index.js' -import { getConnection } from '../orm/connection.js' +import { $, sub, aggregation } from '../index.ts' +import { getConnection } from '../orm/connection.ts' import { afterEachTestGc } from './_helpers.js' import connect from '../connect/test.js' import { isMissingShareDoc } from '../orm/missingDoc.js' diff --git a/packages/teamplay/test/missingDocPlaceholder.js b/packages/teamplay/test/missingDocPlaceholder.js index 7e4b447..9208fd5 100644 --- a/packages/teamplay/test/missingDocPlaceholder.js +++ b/packages/teamplay/test/missingDocPlaceholder.js @@ -1,6 +1,6 @@ import { describe, it, before, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { $, getConnection, sub } from '../index.js' +import { $, getConnection, sub } from '../index.ts' import connect from '../connect/test.js' import { docSubscriptions } from '../orm/Doc.js' diff --git a/packages/teamplay/test/ormAssociations.js b/packages/teamplay/test/ormAssociations.js index 398c246..12973bb 100644 --- a/packages/teamplay/test/ormAssociations.js +++ b/packages/teamplay/test/ormAssociations.js @@ -1,7 +1,7 @@ import { describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { addModel, getRootSignal } from '../index.js' -import BaseModel, { belongsTo, hasMany, hasOne } from '../orm/index.js' +import { addModel, getRootSignal } from '../index.ts' +import BaseModel, { belongsTo, hasMany, hasOne } from '../orm/index.ts' describe('ORM associations', () => { it('exposes getAssociations() on model signals', () => { diff --git a/packages/teamplay/test/publicDocCreateConsistency.js b/packages/teamplay/test/publicDocCreateConsistency.js index 979283b..617966a 100644 --- a/packages/teamplay/test/publicDocCreateConsistency.js +++ b/packages/teamplay/test/publicDocCreateConsistency.js @@ -1,6 +1,6 @@ import { describe, it, before, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { $, getConnection, sub } from '../index.js' +import { $, getConnection, sub } from '../index.ts' import connect from '../connect/test.js' import { docSubscriptions } from '../orm/Doc.js' diff --git a/packages/teamplay/test/publicOnlyCompat.js b/packages/teamplay/test/publicOnlyCompat.js index 4b7d034..cb84623 100644 --- a/packages/teamplay/test/publicOnlyCompat.js +++ b/packages/teamplay/test/publicOnlyCompat.js @@ -1,6 +1,6 @@ import { afterEach, describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { getRootSignal, setPublicOnly } from '../index.js' +import { getRootSignal, setPublicOnly } from '../index.ts' import { __resetRootContextsForTests } from '../orm/rootContext.js' describe('publicOnly', () => { diff --git a/packages/teamplay/test/queryEvents.js b/packages/teamplay/test/queryEvents.js index f232828..2781966 100644 --- a/packages/teamplay/test/queryEvents.js +++ b/packages/teamplay/test/queryEvents.js @@ -1,8 +1,8 @@ import { it, describe, before } from 'mocha' import { strict as assert } from 'node:assert' import { afterEachTestGc, runGc } from './_helpers.js' -import { $, sub } from '../index.js' -import { getConnection } from '../orm/connection.js' +import { $, sub } from '../index.ts' +import { getConnection } from '../orm/connection.ts' import { querySubscriptions } from '../orm/Query.js' import { docSubscriptions } from '../orm/Doc.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test/rootClose.js b/packages/teamplay/test/rootClose.js index 4d32b51..669193f 100644 --- a/packages/teamplay/test/rootClose.js +++ b/packages/teamplay/test/rootClose.js @@ -3,12 +3,12 @@ import { strict as assert } from 'node:assert' import { __DEBUG_SIGNALS_CACHE__ as signalsCache, getRootSignal -} from '../index.js' +} from '../index.ts' import { assertDocSubscriptionsConsistent, assertQuerySubscriptionsConsistent } from './_subscriptionAssertions.js' import connect from '../connect/test.js' import { aggregationSubscriptions } from '../orm/Aggregation.js' import { docSubscriptions } from '../orm/Doc.js' -import { getConnection } from '../orm/connection.js' +import { getConnection } from '../orm/connection.ts' import { del as _del } from '../orm/dataTree.js' import { __resetModelEventsForTests } from '../orm/Compat/modelEvents.js' import { __resetRefLinksForTests } from '../orm/Compat/refRegistry.js' diff --git a/packages/teamplay/test/rootFetchOnly.js b/packages/teamplay/test/rootFetchOnly.js index 813eb0b..06eb5ef 100644 --- a/packages/teamplay/test/rootFetchOnly.js +++ b/packages/teamplay/test/rootFetchOnly.js @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { setDefaultFetchOnly, getDefaultFetchOnly } from '../orm/connection.js' -import { getRootFetchOnly, getRootSignal } from '../orm/Root.js' +import { setDefaultFetchOnly, getDefaultFetchOnly } from '../orm/connection.ts' +import { getRootFetchOnly, getRootSignal } from '../orm/Root.ts' import { __getRootContextForTests, __resetRootContextsForTests } from '../orm/rootContext.js' let previousDefaultFetchOnly diff --git a/packages/teamplay/test/rootFinalization.js b/packages/teamplay/test/rootFinalization.js index 2c246bc..1c71638 100644 --- a/packages/teamplay/test/rootFinalization.js +++ b/packages/teamplay/test/rootFinalization.js @@ -1,11 +1,11 @@ import { before, beforeEach, afterEach, describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { getRootSignal } from '../index.js' +import { getRootSignal } from '../index.ts' import { assertDocSubscriptionsConsistent, assertQuerySubscriptionsConsistent } from './_subscriptionAssertions.js' import connect from '../connect/test.js' import { aggregationSubscriptions } from '../orm/Aggregation.js' import { docSubscriptions } from '../orm/Doc.js' -import { getConnection } from '../orm/connection.js' +import { getConnection } from '../orm/connection.ts' import { del as _del } from '../orm/dataTree.js' import { __resetModelEventsForTests } from '../orm/Compat/modelEvents.js' import { __resetRefLinksForTests } from '../orm/Compat/refRegistry.js' diff --git a/packages/teamplay/test/rootScopeHelpers.js b/packages/teamplay/test/rootScopeHelpers.js index 8c27203..36945ce 100644 --- a/packages/teamplay/test/rootScopeHelpers.js +++ b/packages/teamplay/test/rootScopeHelpers.js @@ -1,6 +1,6 @@ import { describe, it } from 'mocha' import { strict as assert } from 'node:assert' -import { GLOBAL_ROOT_ID } from '../orm/Root.js' +import { GLOBAL_ROOT_ID } from '../orm/Root.ts' import { normalizeRootId, isGlobalRootId, diff --git a/packages/teamplay/test/rootScopedPrivateStorage.js b/packages/teamplay/test/rootScopedPrivateStorage.js index b393a62..67aba61 100644 --- a/packages/teamplay/test/rootScopedPrivateStorage.js +++ b/packages/teamplay/test/rootScopedPrivateStorage.js @@ -1,6 +1,6 @@ import { describe, it, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { getRootSignal } from '../index.js' +import { getRootSignal } from '../index.ts' import { del as _del, set as _set } from '../orm/dataTree.js' import { getPrivateData, getPrivateDataRawRoot } from '../orm/privateData.js' import { __resetRootContextsForTests } from '../orm/rootContext.js' diff --git a/packages/teamplay/test/rootScopedPublicSignals.js b/packages/teamplay/test/rootScopedPublicSignals.js index ca3990f..4620b67 100644 --- a/packages/teamplay/test/rootScopedPublicSignals.js +++ b/packages/teamplay/test/rootScopedPublicSignals.js @@ -1,8 +1,8 @@ import assert from 'assert' import { before, beforeEach, afterEach, describe, it } from 'mocha' -import { addModel, getRootSignal } from '../index.js' +import { addModel, getRootSignal } from '../index.ts' import { docSubscriptions } from '../orm/Doc.js' -import { getConnection } from '../orm/connection.js' +import { getConnection } from '../orm/connection.ts' import { del as _del, set as _set } from '../orm/dataTree.js' import { __resetRefLinksForTests } from '../orm/Compat/refRegistry.js' import { __resetModelEventsForTests } from '../orm/Compat/modelEvents.js' diff --git a/packages/teamplay/test/rootScopedRefsAndEvents.js b/packages/teamplay/test/rootScopedRefsAndEvents.js index 5eb2593..8bb0b88 100644 --- a/packages/teamplay/test/rootScopedRefsAndEvents.js +++ b/packages/teamplay/test/rootScopedRefsAndEvents.js @@ -1,6 +1,6 @@ import { describe, it, afterEach } from 'mocha' import { strict as assert } from 'node:assert' -import { getRootSignal } from '../index.js' +import { getRootSignal } from '../index.ts' import { del as _del, set as _set } from '../orm/dataTree.js' import { __resetModelEventsForTests } from '../orm/Compat/modelEvents.js' import { __resetRefLinksForTests } from '../orm/Compat/refRegistry.js' diff --git a/packages/teamplay/test/signalCompat.js b/packages/teamplay/test/signalCompat.js index 47e15dc..32283e6 100644 --- a/packages/teamplay/test/signalCompat.js +++ b/packages/teamplay/test/signalCompat.js @@ -1,19 +1,19 @@ import { it, describe, afterEach, before, after } from 'mocha' import { strict as assert } from 'node:assert' import { raw, observe, unobserve } from '@nx-js/observer-util' -import { $, sub, addModel, aggregation, getRootSignal } from '../index.js' +import { $, sub, addModel, aggregation, getRootSignal } from '../index.ts' import { get as _get, set as _set, del as _del } from '../orm/dataTree.js' -import { getConnection, setConnection, getDefaultFetchOnly, setDefaultFetchOnly } from '../orm/connection.js' -import getSignal from '../orm/getSignal.js' +import { getConnection, setConnection, getDefaultFetchOnly, setDefaultFetchOnly } from '../orm/connection.ts' +import getSignal from '../orm/getSignal.ts' import connect from '../connect/test.js' import SignalCompat from '../orm/Compat/SignalCompat.js' -import { Signal as BaseSignal } from '../orm/SignalBase.js' +import { Signal as BaseSignal } from '../orm/SignalBase.ts' import { scheduleReaction } from '../orm/batchScheduler.js' import { __resetModelEventsForTests } from '../orm/Compat/modelEvents.js' import { __resetRefLinksForTests } from '../orm/Compat/refRegistry.js' import { __resetSilentContextForTests, isSilentContextActive } from '../orm/Compat/silentContext.js' import { isMissingShareDoc } from '../orm/missingDoc.js' -import { ROOT, ROOT_ID } from '../orm/Root.js' +import { ROOT, ROOT_ID } from '../orm/Root.ts' import { PARAMS, HASH as QUERY_HASH, QUERIES, querySubscriptions } from '../orm/Query.js' import { AGGREGATIONS, aggregationSubscriptions } from '../orm/Aggregation.js' import { delPrivateData, setPrivateData } from '../orm/privateData.js' diff --git a/packages/teamplay/test/sub$.js b/packages/teamplay/test/sub$.js index cbe8b2b..44efdf5 100644 --- a/packages/teamplay/test/sub$.js +++ b/packages/teamplay/test/sub$.js @@ -1,12 +1,12 @@ import { it, describe, afterEach, before } from 'mocha' import { strict as assert } from 'node:assert' import { afterEachTestGc, runGc } from './_helpers.js' -import { $, sub, aggregation } from '../index.js' +import { $, sub, aggregation } from '../index.ts' import { get as _get, del as _del } from '../orm/dataTree.js' -import { getConnection } from '../orm/connection.js' +import { getConnection } from '../orm/connection.ts' import { hashQuery } from '../orm/Query.js' import { getPrivateData } from '../orm/privateData.js' -import { getRoot, ROOT_ID } from '../orm/Root.js' +import { getRoot, ROOT_ID } from '../orm/Root.ts' import connect from '../connect/test.js' before(connect) diff --git a/packages/teamplay/test/subscriptionManagers.js b/packages/teamplay/test/subscriptionManagers.js index 0aa6e4e..0162875 100644 --- a/packages/teamplay/test/subscriptionManagers.js +++ b/packages/teamplay/test/subscriptionManagers.js @@ -14,7 +14,7 @@ import { it, describe, before, beforeEach, afterEach } from 'mocha' import { strict as assert } from 'node:assert' import { afterEachTestGc, runGc } from './_helpers.js' import { assertDocSubscriptionsConsistent, assertQuerySubscriptionsConsistent } from './_subscriptionAssertions.js' -import { $, sub } from '../index.js' +import { $, sub } from '../index.ts' import { docSubscriptions, DocSubscriptions } from '../orm/Doc.js' import { isMissingShareDoc } from '../orm/missingDoc.js' import { @@ -29,10 +29,10 @@ import { hashQuery } from '../orm/Query.js' import { getAggregationSignal, AGGREGATIONS, aggregationSubscriptions } from '../orm/Aggregation.js' -import { SEGMENTS } from '../orm/Signal.js' -import { getConnection } from '../orm/connection.js' +import { SEGMENTS } from '../orm/Signal.ts' +import { getConnection } from '../orm/connection.ts' import { get as _get } from '../orm/dataTree.js' -import { getRootSignal, ROOT_ID } from '../orm/Root.js' +import { getRootSignal, ROOT_ID } from '../orm/Root.ts' import { getPrivateData } from '../orm/privateData.js' import { getScopedSignalHash } from '../orm/rootScope.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test/ts-transform.cjs b/packages/teamplay/test/ts-transform.cjs new file mode 100644 index 0000000..525047c --- /dev/null +++ b/packages/teamplay/test/ts-transform.cjs @@ -0,0 +1,17 @@ +const ts = require('typescript') + +module.exports = { + process (sourceText, sourcePath) { + const result = ts.transpileModule(sourceText, { + fileName: sourcePath, + compilerOptions: { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + sourceMap: false, + inlineSourceMap: false, + importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove + } + }) + return { code: result.outputText } + } +} diff --git a/packages/teamplay/test_client/react-extended.js b/packages/teamplay/test_client/react-extended.js index ea472e9..fb1a198 100644 --- a/packages/teamplay/test_client/react-extended.js +++ b/packages/teamplay/test_client/react-extended.js @@ -44,10 +44,10 @@ import { useDidUpdate, useOnce, useSyncEffect -} from '../index.js' -import { setTestThrottling, resetTestThrottling, useSubClassic } from '../react/useSub.js' +} from '../index.ts' +import { setTestThrottling, resetTestThrottling, useSubClassic } from '../react/useSub.ts' import { __resetSuspendMemoForTests } from '../react/useSuspendMemo.js' -import { useId, useNow, useTriggerUpdate, useUnmount } from '../react/helpers.js' +import { useId, useNow, useTriggerUpdate, useUnmount } from '../react/helpers.ts' import trapRender from '../react/trapRender.js' import renderAttemptDestroyer from '../react/renderAttemptDestroyer.js' import { @@ -57,7 +57,7 @@ import { import { runGc, cache } from '../test/_helpers.js' import { get as _get, set as _set, del as _del } from '../orm/dataTree.js' import connect from '../connect/test.js' -import { SEGMENTS } from '../orm/Signal.js' +import { SEGMENTS } from '../orm/Signal.ts' import { docSubscriptions } from '../orm/Doc.js' import { PARAMS as QUERY_PARAMS, querySubscriptions } from '../orm/Query.js' import { aggregationSubscriptions, AGGREGATIONS } from '../orm/Aggregation.js' diff --git a/packages/teamplay/test_client/react-gc.js b/packages/teamplay/test_client/react-gc.js index 34748ff..1dd12bc 100644 --- a/packages/teamplay/test_client/react-gc.js +++ b/packages/teamplay/test_client/react-gc.js @@ -1,7 +1,7 @@ import { createElement as el, Fragment } from 'react' import { describe, it, afterEach, beforeEach, expect, beforeAll as before } from '@jest/globals' import { act, cleanup, render } from '@testing-library/react' -import { $, useSub, observer, sub, aggregation } from '../index.js' +import { $, useSub, observer, sub, aggregation } from '../index.ts' import { docSubscriptions } from '../orm/Doc.js' import { querySubscriptions } from '../orm/Query.js' import { aggregationSubscriptions } from '../orm/Aggregation.js' diff --git a/packages/teamplay/test_client/react-subscriptions.js b/packages/teamplay/test_client/react-subscriptions.js index 0217190..d28084c 100644 --- a/packages/teamplay/test_client/react-subscriptions.js +++ b/packages/teamplay/test_client/react-subscriptions.js @@ -1,7 +1,7 @@ import { createElement as el, Fragment } from 'react' import { describe, it, afterEach, beforeEach, expect, beforeAll as before } from '@jest/globals' import { act, cleanup, fireEvent, render } from '@testing-library/react' -import { $, useSub, useAsyncSub, observer, sub, aggregation } from '../index.js' +import { $, useSub, useAsyncSub, observer, sub, aggregation } from '../index.ts' import { runGc, cache } from '../test/_helpers.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test_client/react.js b/packages/teamplay/test_client/react.js index 91b37e2..8f1aa86 100644 --- a/packages/teamplay/test_client/react.js +++ b/packages/teamplay/test_client/react.js @@ -1,8 +1,8 @@ import { createElement as el, Fragment, useEffect, useLayoutEffect } from 'react' import { describe, it, afterEach, beforeEach, expect, beforeAll as before } from '@jest/globals' import { act, cleanup, fireEvent, render } from '@testing-library/react' -import { $, useSub, useAsyncSub, observer, sub } from '../index.js' -import { setTestThrottling, resetTestThrottling } from '../react/useSub.js' +import { $, useSub, useAsyncSub, observer, sub } from '../index.ts' +import { setTestThrottling, resetTestThrottling } from '../react/useSub.ts' import { runGc, cache } from '../test/_helpers.js' import connect from '../connect/test.js' diff --git a/packages/teamplay/test_client/session-ref-compat.js b/packages/teamplay/test_client/session-ref-compat.js index 03f0338..ecfb1bb 100644 --- a/packages/teamplay/test_client/session-ref-compat.js +++ b/packages/teamplay/test_client/session-ref-compat.js @@ -1,9 +1,9 @@ import { createElement as el, Fragment } from 'react' import { describe, it, beforeAll as before, afterEach, expect } from '@jest/globals' import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react' -import { $, observer, useSession } from '../index.js' +import { $, observer, useSession } from '../index.ts' import connect from '../connect/test.js' -import { getConnection } from '../orm/connection.js' +import { getConnection } from '../orm/connection.ts' import { del as _del } from '../orm/dataTree.js' const isCompatMode = process.env.TEAMPLAY_COMPAT === '1' diff --git a/packages/teamplay/tsconfig.type-tests.json b/packages/teamplay/tsconfig.type-tests.json index 7219398..c0b9a78 100644 --- a/packages/teamplay/tsconfig.type-tests.json +++ b/packages/teamplay/tsconfig.type-tests.json @@ -3,6 +3,9 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "allowJs": true, + "checkJs": false, "strict": true, "noEmit": true, "skipLibCheck": true, @@ -12,9 +15,12 @@ ] }, "include": [ - "index.d.ts", - "orm/**/*.d.ts", - "react/**/*.d.ts", + "index.ts", + "orm/**/*.ts", + "react/**/*.ts", + "../schema/index.ts", + "../utils/aggregation.ts", + "../utils/accessControl.ts", "test_types/**/*.ts" ] } diff --git a/packages/utils/accessControl.d.ts b/packages/utils/accessControl.d.ts deleted file mode 100644 index b02e71f..0000000 --- a/packages/utils/accessControl.d.ts +++ /dev/null @@ -1 +0,0 @@ -export function accessControl (...args: any[]): any diff --git a/packages/utils/accessControl.js b/packages/utils/accessControl.ts similarity index 92% rename from packages/utils/accessControl.js rename to packages/utils/accessControl.ts index 7a9983c..33c33da 100644 --- a/packages/utils/accessControl.js +++ b/packages/utils/accessControl.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck export const isAccessControlSymbol = Symbol('is access control object') export const OPERATIONS = [ 'create', diff --git a/packages/utils/aggregation.d.ts b/packages/utils/aggregation.d.ts deleted file mode 100644 index 37a7259..0000000 --- a/packages/utils/aggregation.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface AggregationMeta { - readonly __isAggregation: true - readonly collection: TCollection - readonly name: string -} - -export interface AggregationFunction { - (...args: any[]): any - readonly __isAggregation: true - readonly collection: TCollection -} - -export function aggregation ( - collection: TCollection, - fn: (...args: any[]) => any -): AggregationFunction -export function aggregation (fn: (...args: any[]) => any): AggregationFunction -export function aggregationHeader ( - aggregationMeta: { collection: TCollection, name: string } -): AggregationMeta -export function isAggregationHeader (value: unknown): boolean -export function isAggregationFunction (value: unknown): boolean -export function isClientAggregationFunction (value: unknown): boolean diff --git a/packages/utils/aggregation.js b/packages/utils/aggregation.ts similarity index 65% rename from packages/utils/aggregation.js rename to packages/utils/aggregation.ts index 1af6164..59e3ad3 100644 --- a/packages/utils/aggregation.js +++ b/packages/utils/aggregation.ts @@ -1,6 +1,20 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck export const isAggregationFlag = '__isAggregation' export const isClientAggregationFlag = '__isClientAggregation' +export interface AggregationMeta { + readonly __isAggregation: true + readonly collection: TCollection + readonly name: string +} + +export interface AggregationFunction { + (...args: any[]): any + readonly __isAggregation: true + readonly collection: TCollection +} + export function isAggregation (something) { return isAggregationFunction(something) || isAggregationHeader(something) } @@ -19,11 +33,16 @@ export function isAggregationHeader (aggregationMeta) { // this is a universal aggregation function which can be either used on client side or on server // On the client it has arguments like clientAggregation('collectionName', aggregationFn) -export function aggregation (aggregationFn) { - if (typeof aggregationFn === 'string') return clientAggregation(...arguments) - if (typeof aggregationFn !== 'function') throw Error('aggregation: argument must be a function') - aggregationFn[isAggregationFlag] = true - return aggregationFn +export function aggregation ( + collection: TCollection, + fn: (...args: any[]) => any +): AggregationFunction +export function aggregation (fn: (...args: any[]) => any): AggregationFunction +export function aggregation (collectionOrFn, aggregationFn) { + if (typeof collectionOrFn === 'string') return clientAggregation(collectionOrFn, aggregationFn) + if (typeof collectionOrFn !== 'function') throw Error('aggregation: argument must be a function') + collectionOrFn[isAggregationFlag] = true + return collectionOrFn } export function clientAggregation (collection, aggregationFn) { @@ -37,6 +56,9 @@ export function clientAggregation (collection, aggregationFn) { // during compilation, calls to aggregation() are replaced with: // aggregationHeader({ collection: 'collectionName', name: 'aggregationName' }) +export function aggregationHeader ( + aggregationMeta: { collection: TCollection, name: string } +): AggregationMeta export function aggregationHeader (aggregationMeta) { if (!validateAggregationMeta(aggregationMeta)) { throw Error(ERRORS.wrongAggregationMeta(aggregationMeta)) diff --git a/packages/utils/package.json b/packages/utils/package.json index 213a2c4..5776c44 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -5,8 +5,14 @@ "description": "Isomorphic utils for internal cross-package usage", "main": "index.js", "exports": { - "./aggregation": "./aggregation.js", - "./accessControl": "./accessControl.js", + "./aggregation": { + "types": "./aggregation.ts", + "default": "./aggregation.ts" + }, + "./accessControl": { + "types": "./accessControl.ts", + "default": "./accessControl.ts" + }, "./uuid": "./uuid.cjs" }, "dependencies": { diff --git a/plan.md b/plan.md index 4dc9aa6..84896a9 100644 --- a/plan.md +++ b/plan.md @@ -83,3 +83,9 @@ Expected VS Code behavior: - If/when Zod runtime schemas are added, expose a helper that accepts a Zod namespace or converter so `z.toJSONSchema(schema)` can be used without making every runtime consumer load Zod. - Backend validation should continue to receive plain JSON Schema after `transformSchema()`, regardless of whether the source schema is JSON Schema or Zod. +## Direct TypeScript Source Update + +- The migrated files are now distributed as `.ts` source directly, without `.js` re-export shims and without parallel `.d.ts` files. +- Package exports point `types` and `default` at the same `.ts` entrypoints for the converted modules. Runtime imports inside the monorepo use explicit `.ts` extensions when they target converted files. +- Jest cannot use Node's built-in TypeScript stripper from its VM module loader, so client tests use a test-only TypeScript strip transformer. This does not produce build artifacts or change published source. +- `SignalBase.ts` now carries the public method annotations directly on the class implementation so local/computed signal inference does not collapse to `any`. From 2cd58ecbb6b0506e6e8a09efa262e2d0f8d4f4d0 Mon Sep 17 00:00:00 2001 From: Pavel Zhukov Date: Thu, 23 Apr 2026 21:34:48 +0000 Subject: [PATCH 03/17] Modernize lint and test tooling --- docs-theme/index.tsx | 12 +- eslint.config.mjs | 30 + example/_serveClient.js | 6 +- example/{client.js => client.jsx} | 12 +- package.json | 21 +- packages/schema/index.ts | 1 - packages/sharedb-access/package.json | 2 +- packages/sharedb-schema/package.json | 2 +- packages/teamplay/index.ts | 2 - packages/teamplay/orm/Reaction.js | 2 +- packages/teamplay/orm/Root.ts | 1 - packages/teamplay/orm/Signal.ts | 3 +- packages/teamplay/orm/SignalBase.ts | 1 - packages/teamplay/orm/addModel.ts | 1 - packages/teamplay/orm/connection.ts | 1 - packages/teamplay/orm/getSignal.ts | 1 - packages/teamplay/orm/index.ts | 1 - packages/teamplay/orm/sub.ts | 1 - packages/teamplay/package.json | 15 +- packages/teamplay/react/helpers.ts | 3 +- packages/teamplay/react/useSub.ts | 3 +- packages/utils/accessControl.ts | 1 - packages/utils/aggregation.ts | 1 - yarn.lock | 5221 +++++++++++++++----------- 24 files changed, 3097 insertions(+), 2247 deletions(-) create mode 100644 eslint.config.mjs rename example/{client.js => client.jsx} (72%) diff --git a/docs-theme/index.tsx b/docs-theme/index.tsx index 884473a..dde831c 100644 --- a/docs-theme/index.tsx +++ b/docs-theme/index.tsx @@ -7,9 +7,9 @@ export * from '@rspress/core/theme-original' export function Layout () { return ( -

- -
+
+ +
@@ -58,7 +58,7 @@ interface ProjectSidebarProps { function ProjectSidebar ({ activeProject }: ProjectSidebarProps) { return ( -