@cuboapp/crdt 1.0.27 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/client/index.d.ts +77 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/queue/index.d.ts +60 -0
  4. package/dist/client/queue/index.d.ts.map +1 -0
  5. package/dist/client/types/document.d.ts +23 -0
  6. package/dist/client/types/document.d.ts.map +1 -0
  7. package/dist/client/types/index.d.ts +60 -0
  8. package/dist/client/types/index.d.ts.map +1 -0
  9. package/dist/client/types/store.d.ts +17 -0
  10. package/dist/client/types/store.d.ts.map +1 -0
  11. package/dist/client/types/utils.d.ts +13 -0
  12. package/dist/client/types/utils.d.ts.map +1 -0
  13. package/dist/constants/index.d.ts +7 -0
  14. package/dist/constants/index.d.ts.map +1 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/server/document/index.d.ts +50 -0
  18. package/dist/server/document/index.d.ts.map +1 -0
  19. package/dist/server/index.d.ts +77 -0
  20. package/dist/server/index.d.ts.map +1 -0
  21. package/dist/server/types/document.d.ts +59 -0
  22. package/dist/server/types/document.d.ts.map +1 -0
  23. package/dist/server/types/index.d.ts +50 -0
  24. package/dist/server/types/index.d.ts.map +1 -0
  25. package/dist/server/types/subscribe.d.ts +35 -0
  26. package/dist/server/types/subscribe.d.ts.map +1 -0
  27. package/dist/server/utils/index.d.ts +3 -0
  28. package/dist/server/utils/index.d.ts.map +1 -0
  29. package/dist/types/index.d.ts +27 -0
  30. package/dist/types/index.d.ts.map +1 -0
  31. package/dist/utils/index.d.ts +10 -0
  32. package/dist/utils/index.d.ts.map +1 -0
  33. package/package.json +23 -18
  34. package/src/client/index.ts +23 -13
  35. package/src/server/index.ts +24 -13
  36. package/src/server/types/subscribe.ts +2 -2
  37. package/src/types/index.ts +11 -1
  38. package/src/utils/index.ts +5 -2
@@ -0,0 +1,77 @@
1
+ import { CuboCrdtKey, CuboCrdtRowId, CuboCrdtSubscriptionStatus } from '../types';
2
+ import { CuboCrdtClientDoc, CuboCrdtClientDocOrigin, CuboCrdtClientList, CuboCrdtClientOptions, CuboCrdtClientRow, CuboCrdtClientStore, CuboCrdtClientUseOptions } from './types';
3
+ export * from './types';
4
+ export declare class CuboCrdtClient<M> {
5
+ private opts;
6
+ store: CuboCrdtClientStore<M>;
7
+ docs: import("vue").ShallowReactive<Map<string, CuboCrdtClientDoc>>;
8
+ private listeners;
9
+ private subscriptions;
10
+ private onReconnect;
11
+ private onDisconnect;
12
+ private eventQueue;
13
+ constructor(opts: CuboCrdtClientOptions<M>);
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ /**
17
+ * Переподписка на все активные подписки после реконнекта.
18
+ *
19
+ * При обрыве сокета бэкенд удаляет клиента вместе со всеми его подписками
20
+ * (removeClient -> cleanSubscribe), а после реконнекта сокету выдаётся новый id.
21
+ * Локальные слушатели (this.listeners) и yjs-документы при этом сохраняются,
22
+ * поэтому достаточно заново отправить SUBSCRIBE по реестру — заново
23
+ * навешивать слушатели не нужно.
24
+ */
25
+ private resubscribeAll;
26
+ private resolvePaginated;
27
+ /**
28
+ * Подписка на КОЛИЧЕСТВО строк под фильтрами.
29
+ *
30
+ * Возвращает реактивное число и живёт как обычная подписка: сервер пересчитывает
31
+ * count на любую мутацию сущности и присылает только totals — строки не грузятся,
32
+ * CRDT-документы не создаются.
33
+ *
34
+ * Зачем: счётчику (оранжевая точка инбокса, число задач в «Неразобранном») нужно одно
35
+ * число, а обычная подписка ради него держала бы весь набор строк — память на сервере,
36
+ * трафик на каждое их изменение и документы, которые никто не читает.
37
+ *
38
+ * const { count, unsubscribe } = crdt.useCount('events', { filters: { recipient: 5 } })
39
+ */
40
+ useCount<K extends Extract<keyof M, string>>(entity: K | string, opts?: CuboCrdtClientUseOptions): {
41
+ count: import("vue").ComputedRef<number>;
42
+ subscribe_id: string;
43
+ storeKey: string;
44
+ subscribe: (filters?: any) => void;
45
+ upgrade: (filters?: any) => void;
46
+ unsubscribe: (clear?: boolean) => void;
47
+ subscribed: () => import("vue").ComputedRef<boolean>;
48
+ loading: () => import("vue").ComputedRef<boolean>;
49
+ ready: () => import("vue").ComputedRef<boolean>;
50
+ status: () => import("vue").ComputedRef<CuboCrdtSubscriptionStatus>;
51
+ error: () => import("vue").ComputedRef<unknown>;
52
+ totals: () => import("vue").ComputedRef<import("..").CuboCrdtListTotals>;
53
+ revision: () => import("vue").ComputedRef<number>;
54
+ rows: () => import("vue").ComputedRef<CuboCrdtKey<K, M>[]>;
55
+ rowsById: () => import("vue").ComputedRef<Record<string, CuboCrdtKey<K, M>>>;
56
+ };
57
+ useComputedList<K extends Extract<keyof M, string>>(entity: K): import("vue").ComputedRef<M[K][]>;
58
+ useComputedRow<K extends Extract<keyof M, string>>(entity: K, entity_id: CuboCrdtRowId): import("vue").ComputedRef<M[K]>;
59
+ useList<K extends Extract<keyof M, string>, T = CuboCrdtKey<K, M>>(entity: K | string, opts?: CuboCrdtClientUseOptions): CuboCrdtClientList<T>;
60
+ useDoc<K extends Extract<keyof M, string>>(entity: K, id: number): CuboCrdtClientDoc;
61
+ useRow<K extends Extract<keyof M, string>, T = CuboCrdtKey<K, M>>(entity: K | string, id: number, opts?: CuboCrdtClientUseOptions): CuboCrdtClientRow<T>;
62
+ update<K extends Extract<keyof M, string>>(entity: K, entity_id: CuboCrdtRowId, dto: K extends Extract<keyof M, string> ? Partial<M[K]> : object, opts?: CuboCrdtClientDocOrigin): void;
63
+ upgrade(subscribe_id: string, filters?: Record<string, any>): void;
64
+ private applyDocumentRows;
65
+ private attachDocument;
66
+ private detachDocument;
67
+ private applyRowJson;
68
+ private onDocumentCreate;
69
+ private onDocumentUpdate;
70
+ private onDocumentDelete;
71
+ private onIncomingUpdate;
72
+ private onListSync;
73
+ private onAwarenessUpdate;
74
+ private get ws();
75
+ private get debug();
76
+ }
77
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAwB,aAAa,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAA;AAGvG,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EAEnB,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAEhB,cAAc,SAAS,CAAA;AAEvB,qBAAa,cAAc,CAAC,CAAC;IAyCf,OAAO,CAAC,IAAI;IAxCjB,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAK;IAGlC,IAAI,gEAAwD;IAEnE,OAAO,CAAC,SAAS,CAAmG;IAGpH,OAAO,CAAC,aAAa,CAUlB;IACH,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,YAAY,CAYnB;IAED,OAAO,CAAC,UAAU,CAIhB;gBAEkB,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAI5C,KAAK;IAoCL,IAAI;IAeV;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IA6CtB,OAAO,CAAC,gBAAgB;IAUxB;;;;;;;;;;;;OAYG;IACI,QAAQ,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,wBAAwB;;;;2BA1KzB,CAAC;yBAClD,CAAC;2BAAoC,CAAC;;;;;;;;;;;IAmL5D,eAAe,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAI7D,cAAc,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,aAAa;IAQtF,OAAO,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACtE,MAAM,EAAE,CAAC,GAAG,MAAM,EAClB,IAAI,CAAC,EAAE,wBAAwB,GAC9B,kBAAkB,CAAC,CAAC,CAAC;IA0KjB,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM;IAIhE,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrE,MAAM,EAAE,CAAC,GAAG,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE,wBAAwB,GAC9B,iBAAiB,CAAC,CAAC,CAAC;IA4BhB,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAC9C,MAAM,EAAE,CAAC,EACT,SAAS,EAAE,aAAa,EACxB,GAAG,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,EAChE,IAAI,CAAC,EAAE,uBAAuB;IA0BzB,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAyClE,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,cAAc;IA0CtB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,YAAY;YAeN,gBAAgB;YA4FhB,gBAAgB;YAgChB,gBAAgB;YAqChB,gBAAgB;IAqB9B,OAAO,CAAC,UAAU;IAwClB,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,KAAK,EAAE,GAEb;IAED,OAAO,KAAK,KAAK,GAEhB;CACF"}
@@ -0,0 +1,60 @@
1
+ type QueueErrorMode = 'continue' | 'stop';
2
+ export interface AsyncQueueOptions {
3
+ /**
4
+ * Что делать, если задача упала:
5
+ * - continue: логируем/пробрасываем через onError и идём дальше
6
+ * - stop: останавливаем очередь (следующие задачи не стартуют)
7
+ */
8
+ errorMode?: QueueErrorMode;
9
+ /**
10
+ * Хук на ошибки задач (если errorMode=continue — обязательно пригодится)
11
+ */
12
+ onError?: (err: unknown) => void;
13
+ /**
14
+ * Максимальный размер очереди (для защиты от утечек, опционально)
15
+ */
16
+ maxSize?: number;
17
+ }
18
+ export declare class AsyncSerialQueue {
19
+ private running;
20
+ private stopped;
21
+ private readonly errorMode;
22
+ private readonly onError?;
23
+ private readonly maxSize?;
24
+ private tasks;
25
+ private drainWaiters;
26
+ constructor(opts?: AsyncQueueOptions);
27
+ get size(): number;
28
+ get isRunning(): boolean;
29
+ get isStopped(): boolean;
30
+ /**
31
+ * Добавляет задачу в очередь.
32
+ * Возвращает промис, который резолвится когда задача реально выполнена.
33
+ */
34
+ enqueue(fn: () => void | Promise<void>): Promise<void>;
35
+ /**
36
+ * Ждёт, пока очередь станет пустой и ничего не выполняется.
37
+ */
38
+ drain(): Promise<void>;
39
+ /**
40
+ * Очищает очередь (не прерывая текущую выполняемую задачу).
41
+ * Невыполненные задачи получат reject.
42
+ */
43
+ clear(reason?: unknown): void;
44
+ /**
45
+ * Останавливает очередь: новые enqueue будут отвергнуты.
46
+ * Можно опционально очистить pending.
47
+ */
48
+ stop(opts?: {
49
+ clear?: boolean;
50
+ reason?: unknown;
51
+ }): void;
52
+ /**
53
+ * Разрешает снова enqueue (не запускает автоматически).
54
+ */
55
+ resume(): void;
56
+ private pump;
57
+ private notifyDrainedIfNeeded;
58
+ }
59
+ export {};
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/queue/index.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG,UAAU,GAAG,MAAM,CAAA;AAEzC,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAA;IAE1B;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IAEhC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAQ;IAEvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAQ;IAEjC,OAAO,CAAC,KAAK,CAIN;IAEP,OAAO,CAAC,YAAY,CAAwB;gBAEhC,IAAI,GAAE,iBAAsB;IAMxC,IAAI,IAAI,WAEP;IAED,IAAI,SAAS,YAEZ;IAED,IAAI,SAAS,YAEZ;IAED;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBtD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;;OAGG;IACH,KAAK,CAAC,MAAM,GAAE,OAAoC,GAAG,IAAI;IAOzD;;;OAGG;IACH,IAAI,CAAC,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAK5D;;OAEG;IACH,MAAM,IAAI,IAAI;YAKA,IAAI;IA4BlB,OAAO,CAAC,qBAAqB;CAO9B"}
@@ -0,0 +1,23 @@
1
+ import { Awareness } from 'y-protocols/awareness';
2
+ import { Doc } from 'yjs';
3
+ import { CuboCrdtClientUseOptions } from '..';
4
+ import { CuboCrdtExposeStrategy } from '../../types';
5
+ import { CuboCrdtClientBaseOptions } from './utils';
6
+ export type CuboCrdtClientDoc = {
7
+ doc: Doc;
8
+ subscribes: Set<string>;
9
+ awareness?: Awareness;
10
+ awarenessSubscribes: Set<string>;
11
+ };
12
+ export type CuboCrdtClientDocOrigin = {
13
+ store?: boolean;
14
+ react?: boolean;
15
+ keys?: string[];
16
+ expose?: CuboCrdtExposeStrategy;
17
+ subscribe_id?: string;
18
+ };
19
+ export type CuboCrdtClientDocUpdateOptions = {
20
+ subscribe_id: string;
21
+ storeKey: string;
22
+ } & Pick<CuboCrdtClientBaseOptions, 'onBeforeCreate' | 'onAfterCreate' | 'onBeforeUpdate' | 'onAfterUpdate' | 'onBeforeDelete' | 'onAfterDelete'> & Pick<CuboCrdtClientUseOptions, 'awareness'>;
23
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../../src/client/types/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAEzB,OAAO,EAAE,wBAAwB,EAAE,MAAM,IAAI,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,GAAG,CAAA;IACR,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACvB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IAEpC,KAAK,CAAC,EAAE,OAAO,CAAA;IAGf,KAAK,CAAC,EAAE,OAAO,CAAA;IAGf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IAGf,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAG/B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAC5F,yBAAyB,EACzB,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,eAAe,GAAG,gBAAgB,GAAG,eAAe,CAC7G,GACC,IAAI,CAAC,wBAAwB,EAAE,WAAW,CAAC,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { type WsClient } from '@cuboapp/ws';
2
+ import { ComputedRef } from 'vue';
3
+ import { CuboCrdtAction, CuboCrdtListSyncData, CuboCrdtListTotals, CuboCrdtSubscriptionStatus } from '../../types';
4
+ import { CuboCrdtClientBaseOptions } from './utils';
5
+ export * from './document';
6
+ export * from './store';
7
+ export * from './utils';
8
+ export type CuboCrdtClientOptions<M> = {
9
+ ws: WsClient;
10
+ entities: Extract<keyof M, string>[];
11
+ debug?: boolean;
12
+ };
13
+ export type CuboCrdtClientUseOptions = CuboCrdtClientBaseOptions & {
14
+ storeKey?: string;
15
+ filters?: Record<string, any>;
16
+ awareness?: boolean;
17
+ paginated?: boolean;
18
+ /**
19
+ * Подписка только на КОЛИЧЕСТВО строк под фильтрами: строки не грузятся и документы
20
+ * не создаются, приезжает лишь totals. Для счётчиков и индикаторов — см. useCount.
21
+ */
22
+ counted?: boolean;
23
+ };
24
+ export type CuboCrdtClientSubscribeEvent = {
25
+ action: CuboCrdtAction;
26
+ entity: string;
27
+ entity_id?: number;
28
+ subscribe_id?: string;
29
+ data?: number[] | CuboCrdtListSyncData;
30
+ };
31
+ export type CuboCrdtClientList<T> = {
32
+ subscribe_id: string;
33
+ storeKey: string;
34
+ subscribe: (filters?: any) => void;
35
+ upgrade: (filters?: any) => void;
36
+ unsubscribe: (clear?: boolean) => void;
37
+ subscribed: () => ComputedRef<boolean>;
38
+ loading: () => ComputedRef<boolean>;
39
+ ready: () => ComputedRef<boolean>;
40
+ status: () => ComputedRef<CuboCrdtSubscriptionStatus>;
41
+ error: () => ComputedRef<unknown>;
42
+ totals: () => ComputedRef<CuboCrdtListTotals>;
43
+ revision: () => ComputedRef<number>;
44
+ rows: () => ComputedRef<T[]>;
45
+ rowsById: () => ComputedRef<Record<string, T>>;
46
+ };
47
+ export type CuboCrdtClientRow<T> = {
48
+ subscribe_id: string;
49
+ storeKey: string;
50
+ subscribe: () => void;
51
+ upgrade: (filters?: any) => void;
52
+ unsubscribe: (clear?: boolean) => void;
53
+ subscribed: () => ComputedRef<boolean>;
54
+ loading: () => ComputedRef<boolean>;
55
+ ready: () => ComputedRef<boolean>;
56
+ status: () => ComputedRef<CuboCrdtSubscriptionStatus>;
57
+ error: () => ComputedRef<unknown>;
58
+ row: () => ComputedRef<T>;
59
+ };
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Reactive } from 'vue';
2
+ import { CuboCrdtKey, CuboCrdtListTotals, CuboCrdtSubscriptionStatus } from '../../types';
3
+ export type CuboCrdtClientStoreItem<T> = {
4
+ state: Reactive<{
5
+ rows: T[];
6
+ subscribed: boolean;
7
+ loading: boolean;
8
+ status: CuboCrdtSubscriptionStatus;
9
+ error?: unknown;
10
+ totals: CuboCrdtListTotals;
11
+ revision: number;
12
+ }>;
13
+ };
14
+ export type CuboCrdtClientStore<M> = Partial<{
15
+ [K in string]: CuboCrdtClientStoreItem<CuboCrdtKey<K, M>>;
16
+ }>;
17
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/client/types/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAA;AAE9B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAEzF,MAAM,MAAM,uBAAuB,CAAC,CAAC,IAAI;IACvC,KAAK,EAAE,QAAQ,CAAC;QACd,IAAI,EAAE,CAAC,EAAE,CAAA;QACT,UAAU,EAAE,OAAO,CAAA;QACnB,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,EAAE,0BAA0B,CAAA;QAClC,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,MAAM,EAAE,kBAAkB,CAAA;QAC1B,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAC,CAAA;CACH,CAAA;AACD,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,OAAO,CAAC;KAC1C,CAAC,IAAI,MAAM,GAAG,uBAAuB,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC1D,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { Doc } from 'yjs';
2
+ import { CuboCrdtClientSubscribeEvent } from '.';
3
+ import { CuboCrdtClientDocUpdateOptions } from './document';
4
+ export type CuboCrdtClientBaseOptions = {
5
+ autoSubscribe?: boolean;
6
+ onBeforeCreate?: (update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<boolean> | boolean;
7
+ onAfterCreate?: (doc: Doc, update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<void> | void;
8
+ onBeforeUpdate?: (doc: Doc, update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<boolean> | boolean;
9
+ onAfterUpdate?: (doc: Doc, update: Uint8Array, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<void> | void;
10
+ onBeforeDelete?: (doc: Doc, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<boolean> | boolean;
11
+ onAfterDelete?: (doc: Doc, ctx: CuboCrdtClientSubscribeEvent, opts: CuboCrdtClientDocUpdateOptions) => Promise<void> | void;
12
+ };
13
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/client/types/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AACzB,OAAO,EAAE,4BAA4B,EAAE,MAAM,GAAG,CAAA;AAChD,OAAO,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAA;AAE3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,4BAA4B,EACjC,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,4BAA4B,EACjC,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAEzB,cAAc,CAAC,EAAE,CACf,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,4BAA4B,EACjC,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,4BAA4B,EACjC,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAEzB,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,4BAA4B,EAAE,IAAI,EAAE,8BAA8B,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAClI,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,4BAA4B,EAAE,IAAI,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC5H,CAAA"}
@@ -0,0 +1,7 @@
1
+ export declare const CUBO_CRDT_EVENT: {
2
+ EVENT: string;
3
+ SUBSCRIBE: string;
4
+ UPGRADE: string;
5
+ UNSUBSCRIBE: string;
6
+ };
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;;CAK3B,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './client';
2
+ export * from './server';
3
+ export * from './types';
4
+ export * from './utils';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { Awareness } from 'y-protocols/awareness';
2
+ import { CuboCrdtServerDocumentOptions, CuboCrdtServerDocumentOrigin } from '../types';
3
+ export declare class CuboCrdtServerDocument {
4
+ opts: CuboCrdtServerDocumentOptions;
5
+ private ydoc;
6
+ private persistedState;
7
+ private observedState;
8
+ private storeQueue?;
9
+ private pendingStore?;
10
+ private storeTimeout?;
11
+ awareness: Awareness;
12
+ awarenessBySubscribe: Map<string, Set<number>>;
13
+ constructor(opts: CuboCrdtServerDocumentOptions);
14
+ enableAwareness(): void;
15
+ get name(): string;
16
+ get stateAsUpdate(): Uint8Array<ArrayBufferLike>;
17
+ applyUpdate(update: Uint8Array, origin?: CuboCrdtServerDocumentOrigin): void;
18
+ getMap(): import("yjs").Map<unknown>;
19
+ getXmlFragment(name?: string): import("yjs").XmlFragment;
20
+ getJson(): {
21
+ [x: string]: any;
22
+ };
23
+ getPersistedState(): any;
24
+ setPersistedState(row: object): void;
25
+ write(dto: object, origin?: CuboCrdtServerDocumentOrigin): void;
26
+ get id(): string;
27
+ destroy(): void;
28
+ /**
29
+ * Немедленно передаёт trailing-пакет в очередь и ждёт все уже принятые
30
+ * сохранения документа. Используется перед вытеснением Y.Doc из серверного
31
+ * кэша, чтобы refresh последнего клиента не потерял его update.
32
+ */
33
+ flushStoreQueue(): Promise<void>;
34
+ /** Есть ли принятое клиентское изменение в debounce или очереди записи. */
35
+ hasPendingStore(): boolean;
36
+ store(body: object, origin: CuboCrdtServerDocumentOrigin, row?: {
37
+ [x: string]: any;
38
+ }): Promise<void>;
39
+ /**
40
+ * Схлопывает только соседние изменения одного origin и одного набора
41
+ * leaf-путей. Смена пользователя или поля немедленно фиксирует предыдущий
42
+ * пакет, сохраняя фактический порядок collaborative-обновлений.
43
+ */
44
+ debounceStore(body: object, origin: CuboCrdtServerDocumentOrigin): Promise<void>;
45
+ /** Перезапускает trailing debounce для текущего однородного пакета. */
46
+ private schedulePendingStore;
47
+ /** Передаёт накопленный пакет в общую последовательную очередь записи. */
48
+ private flushPendingStore;
49
+ }
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/document/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAyB,MAAM,uBAAuB,CAAA;AAExE,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAE7B,MAAM,UAAU,CAAA;AAKjB,qBAAa,sBAAsB;IAYd,IAAI,EAAE,6BAA6B;IAXtD,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,UAAU,CAAC,CAAe;IAClC,OAAO,CAAC,YAAY,CAAC,CAAoC;IACzD,OAAO,CAAC,YAAY,CAAC,CAA+B;IAC7C,SAAS,EAAE,SAAS,CAAA;IACpB,oBAAoB,2BAAiC;gBAIzC,IAAI,EAAE,6BAA6B;IA2C/C,eAAe;IA6BtB,IAAW,IAAI,WAEd;IAED,IAAW,aAAa,gCAEvB;IAED,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,4BAA4B;IAIrE,MAAM;IAIN,cAAc,CAAC,IAAI,SAAK;IAIxB,OAAO;;;IAIP,iBAAiB;IAIjB,iBAAiB,CAAC,GAAG,EAAE,MAAM;IAI7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,4BAA4B;IAoBxD,IAAI,EAAE,WAEL;IAED,OAAO;IAKP;;;;OAIG;IACU,eAAe;IAS5B,2EAA2E;IACpE,eAAe;IAIf,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,EAAE,GAAG;;KAAiB;IA8BrF;;;;OAIG;IACI,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B;IAoDvE,uEAAuE;IACvE,OAAO,CAAC,oBAAoB;IAQ5B,0EAA0E;IAC1E,OAAO,CAAC,iBAAiB;CAkB1B"}
@@ -0,0 +1,77 @@
1
+ import { WsServerSocket } from '@cuboapp/ws';
2
+ import { CuboCrdtMutation, CuboCrdtRowId } from '../types';
3
+ import { CuboCrdtClientDocOrigin } from '../client';
4
+ import { CuboCrdtServerDocument } from './document';
5
+ import { CuboCrdtServerOptions } from './types';
6
+ export * from './document';
7
+ export * from './types';
8
+ export declare class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extract<keyof M, string>> {
9
+ private options;
10
+ constructor(options: CuboCrdtServerOptions<M, A, E>);
11
+ private clients;
12
+ private queue;
13
+ private sendTimeouts;
14
+ private subscribes;
15
+ private subscribesByClient;
16
+ private subscribesByEntity;
17
+ private subscribeRefreshStates;
18
+ private subscribeGenerations;
19
+ private documents;
20
+ private documentEvictions;
21
+ private subscribesByDocument;
22
+ private documentsBySubscribe;
23
+ init(): Promise<void>;
24
+ destroy(): Promise<void>;
25
+ addClient(client: WsServerSocket<{
26
+ auth?: A;
27
+ }>): void;
28
+ removeClient(client: WsServerSocket): void;
29
+ private get batchMaxSize();
30
+ private get batchDebounce();
31
+ private get paginationDebounce();
32
+ private resolvePaginated;
33
+ private sendQueueToClient;
34
+ sendToClient(client_id: string, data: any): void;
35
+ private linkSubscribeToDocument;
36
+ private unlinkSubscribeFromDocument;
37
+ private checkDocumentNeedRemove;
38
+ private cleanSubscribe;
39
+ getDocument(entity: string, entity_id: CuboCrdtRowId): CuboCrdtServerDocument;
40
+ getSutableSubscribes(entity: string, row: any): string[];
41
+ private getSubscribeRefreshState;
42
+ private bumpSubscribeGeneration;
43
+ private runSubscribeRefresh;
44
+ private requestSubscribeRefresh;
45
+ private getSubscribeQueryKeys;
46
+ private shouldRefreshSubscribe;
47
+ /**
48
+ * Уведомляет пагинированные подписки о сохранённой мутации.
49
+ * Пересчитываются только реально открытые страницы; несколько мутаций
50
+ * схлопываются debounce-ом в один запрос на подписку.
51
+ */
52
+ notifyMutation(entity: E, mutation: CuboCrdtMutation<M[E]>): void;
53
+ /**
54
+ * Принудительно инвалидирует все открытые пагинированные окна сущности.
55
+ * Используется для вычисляемых полей и зависимых сущностей, когда changedKeys
56
+ * исходной строки недостаточно для определения влияния на запрос.
57
+ */
58
+ invalidate(entity: E): void;
59
+ private getOrCreateDocument;
60
+ ensureDocument(entity: E, row: any, awareness?: boolean): CuboCrdtServerDocument;
61
+ private sendDocumentToSubscribe;
62
+ deleteDocument(entity: E, row: any, origin?: CuboCrdtClientDocOrigin): void;
63
+ private onDocumentExternalCreate;
64
+ private onDocumentExternalUpdate;
65
+ private onAwarenessExternalUpdate;
66
+ private onDocumentExternalDelete;
67
+ private checkSubscribeIsSutable;
68
+ private checkRowIsSutable;
69
+ private pushDocumentAction;
70
+ private reconcileSubscribe;
71
+ private upgradeSubscribe;
72
+ private initSubscribe;
73
+ private get debug();
74
+ private get ws();
75
+ private get entities();
76
+ }
77
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { CuboCrdtAction, CuboCrdtExposeStrategy } from '../../types';
2
+ export type CuboCrdtServerDocumentStoreContext = {
3
+ previousRow: object;
4
+ row: object;
5
+ };
6
+ export type CuboCrdtServerDocumentStoreWaiter = {
7
+ resolve: () => void;
8
+ reject: (reason?: unknown) => void;
9
+ };
10
+ export type CuboCrdtServerDocumentPendingStore = {
11
+ body: object;
12
+ changeKey: string;
13
+ origin: CuboCrdtServerDocumentOrigin;
14
+ originKey: string;
15
+ row: object;
16
+ waiters: CuboCrdtServerDocumentStoreWaiter[];
17
+ };
18
+ export type CuboCrdtServerDocumentOptions = {
19
+ name: string;
20
+ onStore?: (body: object, origin: CuboCrdtServerDocumentOrigin, context: CuboCrdtServerDocumentStoreContext) => void | Promise<void>;
21
+ onUpdate?: (data: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) => void;
22
+ onAwarenessUpdate?: (data: Uint8Array, origin?: CuboCrdtServerDocumentOrigin) => void;
23
+ initialState?: object;
24
+ awareness?: boolean;
25
+ yjsOptions?: {
26
+ guid?: string;
27
+ collectionid?: string;
28
+ gc?: boolean;
29
+ gcFilter?: () => true;
30
+ meta?: any;
31
+ };
32
+ };
33
+ export type CuboCrdtServerDocumentIncomingAction = {
34
+ action: CuboCrdtAction;
35
+ entity: string;
36
+ entity_id: number;
37
+ data: number[];
38
+ origin: CuboCrdtServerDocumentOrigin;
39
+ };
40
+ export type CuboCrdtServerDocumentOrigin = {
41
+ store?: boolean;
42
+ react?: boolean;
43
+ keys?: string[];
44
+ expose?: CuboCrdtExposeStrategy;
45
+ subscribe_id?: string;
46
+ client_id?: string;
47
+ /**
48
+ * Снимок серверного контекста автора в момент получения update.
49
+ *
50
+ * Он нужен только для отложенного сохранения: исходная подписка и сокет могут
51
+ * закрыться при refresh раньше, чем сработает debounce. Клиентское значение
52
+ * этого поля сервер всегда перезаписывает собственными данными.
53
+ */
54
+ store_context?: {
55
+ client_id: string;
56
+ auth?: unknown;
57
+ };
58
+ };
59
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../../src/server/types/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEpE,MAAM,MAAM,kCAAkC,GAAG;IAC/C,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,iCAAiC,GAAG;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,4BAA4B,CAAA;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,iCAAiC,EAAE,CAAA;CAC7C,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,4BAA4B,EACpC,OAAO,EAAE,kCAAkC,KACxC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,4BAA4B,KAAK,IAAI,CAAA;IAC5E,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,4BAA4B,KAAK,IAAI,CAAA;IAGrF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,EAAE,CAAC,EAAE,OAAO,CAAA;QACZ,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;QACrB,IAAI,CAAC,EAAE,GAAG,CAAA;KACX,CAAA;CACF,CAAA;AAED,MAAM,MAAM,oCAAoC,GAAG;IAEjD,MAAM,EAAE,cAAc,CAAA;IAGtB,MAAM,EAAE,MAAM,CAAA;IAGd,SAAS,EAAE,MAAM,CAAA;IAGjB,IAAI,EAAE,MAAM,EAAE,CAAA;IAGd,MAAM,EAAE,4BAA4B,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG;IAEzC,KAAK,CAAC,EAAE,OAAO,CAAA;IAGf,KAAK,CAAC,EAAE,OAAO,CAAA;IAGf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IAGf,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAG/B,YAAY,CAAC,EAAE,MAAM,CAAA;IAGrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE;QACd,SAAS,EAAE,MAAM,CAAA;QACjB,IAAI,CAAC,EAAE,OAAO,CAAA;KACf,CAAA;CACF,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { WsServer, WsServerSocket } from '@cuboapp/ws';
2
+ import { CuboCrdtListTotals, CuboCrdtMutation } from '../../types';
3
+ import { CuboCrdtServerDocument } from '../document';
4
+ import { CuboCrdtServerDocumentOrigin } from './document';
5
+ import { CuboCrdtServerSubscribe } from './subscribe';
6
+ export * from './document';
7
+ export * from './subscribe';
8
+ export type CuboCrdtServerFetchResult<T> = {
9
+ rows: T[];
10
+ totals?: CuboCrdtListTotals;
11
+ };
12
+ export type CuboCrdtServerOptions<M, A, E extends Extract<keyof M, string>> = {
13
+ ws: WsServer;
14
+ entities: E[];
15
+ debug?: boolean;
16
+ /**
17
+ * Параметры батчинга исходящих событий (защита от перегрузки бэка).
18
+ * - maxSize: при достижении такого размера очереди клиента батч отправляется сразу
19
+ * - debounce: задержка перед отправкой неполного батча (мс)
20
+ */
21
+ batch?: {
22
+ maxSize?: number;
23
+ debounce?: number;
24
+ };
25
+ pagination?: {
26
+ debounce?: number;
27
+ };
28
+ fetchRows?: (client: WsServerSocket, subscribe: CuboCrdtServerSubscribe) => Promise<M[E][] | CuboCrdtServerFetchResult<M[E]>>;
29
+ checkRowIsSutable?: (baseState: boolean, ctx: {
30
+ entity: E;
31
+ subscribe: CuboCrdtServerSubscribe;
32
+ row: any;
33
+ }) => boolean;
34
+ shouldRefreshSubscribe?: (ctx: {
35
+ entity: E;
36
+ subscribe: CuboCrdtServerSubscribe;
37
+ mutation: CuboCrdtMutation<M[E]>;
38
+ }) => boolean;
39
+ storeRow?: <K extends E>(entity: K, entity_id: number, row: M[K], opts: {
40
+ document: CuboCrdtServerDocument;
41
+ client: CuboCrdtSocketClient<A>;
42
+ origin: CuboCrdtServerDocumentOrigin;
43
+ previousRow: M[K];
44
+ row: M[K];
45
+ }) => Promise<void> | void;
46
+ };
47
+ export type CuboCrdtSocketClient<A> = WsServerSocket<{
48
+ auth?: A;
49
+ }>;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAErD,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAE3B,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IACzC,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,qBAAqB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI;IAC5E,EAAE,EAAE,QAAQ,CAAA;IACZ,QAAQ,EAAE,CAAC,EAAE,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,UAAU,CAAC,EAAE;QACX,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,SAAS,CAAC,EAAE,CACV,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,uBAAuB,KAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE;QAAE,MAAM,EAAE,CAAC,CAAC;QAAC,SAAS,EAAE,uBAAuB,CAAC;QAAC,GAAG,EAAE,GAAG,CAAA;KAAE,KAAK,OAAO,CAAA;IACrH,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE;QAC7B,MAAM,EAAE,CAAC,CAAA;QACT,SAAS,EAAE,uBAAuB,CAAA;QAClC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;KACjC,KAAK,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,EACrB,MAAM,EAAE,CAAC,EACT,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EACT,IAAI,EAAE;QACJ,QAAQ,EAAE,sBAAsB,CAAA;QAChC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAA;QAC/B,MAAM,EAAE,4BAA4B,CAAA;QACpC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACjB,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;KACV,KACE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,cAAc,CAAC;IAAE,IAAI,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA"}
@@ -0,0 +1,35 @@
1
+ import type { CuboCrdtListTotals, CuboCrdtRowId } from '../../types';
2
+ export type CuboCrdtServerUnsubscribeDto = {
3
+ subscribe_id: string;
4
+ };
5
+ export type CuboCrdtServerSubscribeDto = {
6
+ subscribe_id: string;
7
+ entity: string;
8
+ filters: {
9
+ [K in 'id' | string]: any;
10
+ };
11
+ awareness?: boolean;
12
+ paginated?: boolean;
13
+ /**
14
+ * Подписка только на КОЛИЧЕСТВО строк под фильтрами.
15
+ *
16
+ * Документы при этом не создаются вовсе: клиенту нужно одно число (счётчик,
17
+ * индикатор непрочитанного), а держать ради него весь набор строк — держать
18
+ * лишнюю память на сервере и лишний трафик на каждое их изменение.
19
+ */
20
+ counted?: boolean;
21
+ };
22
+ export type CuboCrdtServerSubscribe = {
23
+ id: string;
24
+ client_id: string;
25
+ row_ids: CuboCrdtRowId[];
26
+ totals: CuboCrdtListTotals;
27
+ revision: number;
28
+ } & Pick<CuboCrdtServerSubscribeDto, 'entity' | 'filters' | 'awareness' | 'paginated' | 'counted'>;
29
+ export type CuboCrdtSubscribeRefreshState = {
30
+ dirty: boolean;
31
+ forceSync: boolean;
32
+ timer?: ReturnType<typeof setTimeout>;
33
+ running?: Promise<void>;
34
+ };
35
+ //# sourceMappingURL=subscribe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../../src/server/types/subscribe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEpE,MAAM,MAAM,4BAA4B,GAAG;IACzC,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AACD,MAAM,MAAM,0BAA0B,GAAG;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;SACN,CAAC,IAAI,IAAI,GAAG,MAAM,GAAG,GAAG;KAC1B,CAAA;IACD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,IAAI,CAAC,0BAA0B,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC,CAAA;AAElG,MAAM,MAAM,6BAA6B,GAAG;IAC1C,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** Возвращает стабильный ключ фактически изменённых leaf-путей объекта. */
2
+ export declare const getCrdtStoreChangeKey: (previous: object, current: object) => string;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/utils/index.ts"],"names":[],"mappings":"AAiCA,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,EAAE,SAAS,MAAM,WAMtE,CAAA"}
@@ -0,0 +1,27 @@
1
+ export type CuboCrdtKey<K, M> = K extends Extract<keyof M, string> ? M[K] : any;
2
+ export type CuboCrdtAction = 'create' | 'upsert' | 'update' | 'delete' | 'awareness' | 'sync';
3
+ export type CuboCrdtExposeStrategy = 'all' | 'other' | 'subscribe' | 'client';
4
+ export type CuboCrdtListTotals = Record<string, number>;
5
+ export type CuboCrdtSubscriptionStatus = 'idle' | 'loading' | 'ready' | 'error';
6
+ /**
7
+ * A row's primary key, as it comes off the wire.
8
+ *
9
+ * Deliberately NOT `number`. Rows are keyed by whatever the database column holds — uuid text in
10
+ * current schemas, integers in older ones — and the CRDT plane only ever compares and indexes ids, it
11
+ * never does arithmetic on them. Typing this as `number` is what let `Number(id)` / `Number.isFinite`
12
+ * creep into both ends of the sync, where every uuid failed the test and the row was silently dropped.
13
+ */
14
+ export type CuboCrdtRowId = string | number;
15
+ export type CuboCrdtListSyncData = {
16
+ ids: CuboCrdtRowId[];
17
+ totals: CuboCrdtListTotals;
18
+ revision: number;
19
+ };
20
+ export type CuboCrdtMutationAction = 'create' | 'update' | 'delete';
21
+ export type CuboCrdtMutation<T = any> = {
22
+ action: CuboCrdtMutationAction;
23
+ row: T;
24
+ previousRow?: T;
25
+ changedKeys?: string[];
26
+ };
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAA;AAE/E,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAE7F,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAA;AAE7E,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEvD,MAAM,MAAM,0BAA0B,GAClC,MAAM,GACN,SAAS,GACT,OAAO,GACP,OAAO,CAAA;AAEX;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAA;AAE3C,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,aAAa,EAAE,CAAA;IACpB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEnE,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,GAAG,IAAI;IACtC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,GAAG,EAAE,CAAC,CAAA;IACN,WAAW,CAAC,EAAE,CAAC,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { CuboApiEntitiesMap } from '@cuboapp/types';
2
+ import { CuboCrdtClient, CuboCrdtClientOptions, CuboCrdtRowId, CuboCrdtServer, CuboCrdtServerDocumentOrigin, CuboCrdtServerOptions, CuboCrdtServerSubscribe } from '..';
3
+ export declare function createCrdtServer<M extends CuboApiEntitiesMap<M>, A = {}, E extends Extract<keyof M, string> = Extract<keyof M, string>>(opts: CuboCrdtServerOptions<M, A, E>): CuboCrdtServer<M, A, E>;
4
+ export declare function createCrdtClient<M extends CuboApiEntitiesMap<M>>(opts: CuboCrdtClientOptions<M>): CuboCrdtClient<M>;
5
+ export declare function cleanObject<F>(object: F): any;
6
+ export declare function checkRowIsSutable(row: any, filters?: object, customKeys?: Record<string, (key: string, row: any, filters?: any) => boolean>): boolean;
7
+ export declare function checkSubscribeIsSutable(origin: CuboCrdtServerDocumentOrigin, subscribe?: CuboCrdtServerSubscribe): boolean;
8
+ export declare function areCrdtListIdsEqual(left: CuboCrdtRowId[], right: CuboCrdtRowId[]): boolean;
9
+ export declare function areCrdtListTotalsEqual(left: Record<string, number>, right: Record<string, number>): boolean;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,IAAI,CAAA;AAEX,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EACrI,IAAI,EAAE,qBAAqB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,2BAGrC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,kBAAkB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,qBAE/F;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAkB7C;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,WAwD/E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,EAAE,SAAS,CAAC,EAAE,uBAAuB,WAWhH;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,WAIhF;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAI9B"}
package/package.json CHANGED
@@ -1,35 +1,40 @@
1
1
  {
2
2
  "name": "@cuboapp/crdt",
3
- "version": "1.0.27",
4
- "description": "CRDT for CUBO",
5
- "main": "src/index.ts",
6
- "repository": {
7
- "type": "git",
8
- "url": "git@cuboapp.gitlab.yandexcloud.net:cubo/crdt.git"
9
- },
10
- "author": "CuboSoft",
3
+ "version": "2.0.0",
4
+ "description": "Yjs-based CRDT server/client over @cuboapp/ws (vendored — README §5/§13.1).",
11
5
  "license": "MIT",
12
6
  "type": "module",
13
- "publishConfig": {
14
- "access": "public"
7
+ "main": "src/index.ts",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./src/index.ts"
13
+ }
15
14
  },
16
15
  "files": [
16
+ "dist",
17
17
  "src"
18
18
  ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "typecheck": "echo \"vendored @cuboapp — typecheck via emitted d.ts\"",
22
+ "test": "echo \"no tests\"",
23
+ "lint": "echo \"no lint\""
24
+ },
19
25
  "dependencies": {
20
- "@cuboapp/types": "^2.0.14",
21
- "@cuboapp/utils": "1.0.12",
22
- "@cuboapp/ws": "1.0.10",
26
+ "@cuboapp/types": "^3.0.0",
27
+ "@cuboapp/utils": "^1.0.12",
28
+ "@cuboapp/ws": "^2.0.0",
23
29
  "y-protocols": "^1.0.7",
24
30
  "yjs": "^13.6.31"
25
31
  },
26
32
  "peerDependencies": {
27
- "vue": "^3.5.38",
28
- "vue-router": "^5.1.0"
33
+ "vue": "^3.5.0"
29
34
  },
30
35
  "devDependencies": {
31
- "@types/node": "^26.0.0",
32
- "@types/ws": "^8.18.1",
33
- "typescript": "^6.0.3"
36
+ "@types/node": "^22.10.5",
37
+ "typescript": "^5.8.3",
38
+ "vue": "^3.5.13"
34
39
  }
35
40
  }
@@ -6,7 +6,7 @@ import { applyUpdate, Doc } from 'yjs'
6
6
 
7
7
  import { CUBO_CRDT_EVENT } from '../constants'
8
8
  import { CuboCrdtServerDocumentIncomingAction } from '../server'
9
- import { CuboCrdtKey, CuboCrdtListSyncData, CuboCrdtSubscriptionStatus } from '../types'
9
+ import { CuboCrdtKey, CuboCrdtListSyncData, CuboCrdtRowId, CuboCrdtSubscriptionStatus } from '../types'
10
10
 
11
11
  import { AsyncSerialQueue } from './queue'
12
12
  import {
@@ -210,9 +210,11 @@ export class CuboCrdtClient<M> {
210
210
  return computed(() => (this.store[entity]?.state.rows || []) as M[K][])
211
211
  }
212
212
 
213
- public useComputedRow<K extends Extract<keyof M, string>>(entity: K, entity_id: number) {
213
+ public useComputedRow<K extends Extract<keyof M, string>>(entity: K, entity_id: CuboCrdtRowId) {
214
214
  return computed(() => {
215
- return ((this.store[entity]?.state.rows || []) as M[K][]).find((row: any) => row.id === entity_id)
215
+ // Compared as strings for the same reason the sync path is: the caller may hold a uuid while the
216
+ // row carries one too, but a legacy numeric id can arrive as either, and `'42' === 42` is false.
217
+ return ((this.store[entity]?.state.rows || []) as M[K][]).find((row: any) => String(row.id) === String(entity_id))
216
218
  })
217
219
  }
218
220
 
@@ -427,7 +429,7 @@ export class CuboCrdtClient<M> {
427
429
 
428
430
  public update<K extends Extract<keyof M, string>>(
429
431
  entity: K,
430
- entity_id: number,
432
+ entity_id: CuboCrdtRowId,
431
433
  dto: K extends Extract<keyof M, string> ? Partial<M[K]> : object,
432
434
  opts?: CuboCrdtClientDocOrigin
433
435
  ) {
@@ -496,7 +498,7 @@ export class CuboCrdtClient<M> {
496
498
  })
497
499
  }
498
500
 
499
- private applyDocumentRows(entity_id: number, document: CuboCrdtClientDoc) {
501
+ private applyDocumentRows(entity_id: CuboCrdtRowId, document: CuboCrdtClientDoc) {
500
502
  for (const subscribe_id of document.subscribes) {
501
503
  const subscription = this.subscriptions.get(subscribe_id)
502
504
  if (subscription) {
@@ -505,7 +507,7 @@ export class CuboCrdtClient<M> {
505
507
  }
506
508
  }
507
509
 
508
- private attachDocument(entity: string, entity_id: number, document: CuboCrdtClientDoc, opts: CuboCrdtClientDocUpdateOptions) {
510
+ private attachDocument(entity: string, entity_id: CuboCrdtRowId, document: CuboCrdtClientDoc, opts: CuboCrdtClientDocUpdateOptions) {
509
511
  document.subscribes.add(opts.subscribe_id)
510
512
 
511
513
  if (!opts.awareness) {
@@ -547,7 +549,7 @@ export class CuboCrdtClient<M> {
547
549
  }
548
550
  }
549
551
 
550
- private detachDocument(entity: string, entity_id: number, subscribe_id: string) {
552
+ private detachDocument(entity: string, entity_id: CuboCrdtRowId, subscribe_id: string) {
551
553
  const key = `${entity}:${entity_id}`
552
554
  const document = this.docs.get(key)
553
555
  if (!document) {
@@ -571,7 +573,7 @@ export class CuboCrdtClient<M> {
571
573
  }
572
574
 
573
575
  // апсертит строку в реактивное хранилище из текущего состояния yjs-документа
574
- private applyRowJson(storeKey: string, entity_id: number, doc: Doc) {
576
+ private applyRowJson(storeKey: string, entity_id: CuboCrdtRowId, doc: Doc) {
575
577
  const json: any = doc.getMap().toJSON()
576
578
  const rows = this.store[storeKey]?.state.rows
577
579
  if (!rows) {
@@ -775,21 +777,29 @@ export class CuboCrdtClient<M> {
775
777
  return
776
778
  }
777
779
 
778
- const ids = data.ids.filter((id) => Number.isFinite(id))
779
- const idsSet = new Set(ids)
780
+ // An id is OPAQUE. This used to be `filter(Number.isFinite)`, from when rows had integer ids —
781
+ // against uuids every id failed the test, `ids` came back empty, and the splice below then wiped
782
+ // the whole list. Silently: `totals` is copied from the payload separately, so the subscription
783
+ // reported the true count while holding no rows, which reads exactly like "the account has none".
784
+ //
785
+ // Compared as STRINGS throughout: the wire carries whatever the database column holds — uuid text
786
+ // today, integers in older data — and `'42' !== 42` would resurrect the same bug for anyone still
787
+ // on numeric ids.
788
+ const ids = data.ids.filter((id) => id !== null && id !== undefined && id !== '')
789
+ const idsSet = new Set(ids.map((id) => String(id)))
780
790
 
781
791
  for (const row of item.state.rows) {
782
792
  const entity_id = (row as any).id
783
793
 
784
- if (idsSet.has(entity_id)) {
794
+ if (idsSet.has(String(entity_id))) {
785
795
  continue
786
796
  }
787
797
 
788
798
  this.detachDocument(ctx.entity, entity_id, opts.subscribe_id)
789
799
  }
790
800
 
791
- const rowsById = new Map(item.state.rows.map((row: any) => [Number(row.id), row]))
792
- const orderedRows = ids.map((id) => rowsById.get(id)).filter((row) => row !== undefined)
801
+ const rowsById = new Map(item.state.rows.map((row: any) => [String(row.id), row]))
802
+ const orderedRows = ids.map((id) => rowsById.get(String(id))).filter((row) => row !== undefined)
793
803
 
794
804
  item.state.rows.splice(0, item.state.rows.length, ...orderedRows)
795
805
  item.state.totals = data.totals || {}
@@ -3,7 +3,7 @@ import { WsServerSocket } from '@cuboapp/ws'
3
3
  import { applyAwarenessUpdate, encodeAwarenessUpdate, removeAwarenessStates } from 'y-protocols/awareness'
4
4
 
5
5
  import { CUBO_CRDT_EVENT } from '../constants'
6
- import { CuboCrdtAction, CuboCrdtListSyncData, CuboCrdtMutation } from '../types'
6
+ import { CuboCrdtAction, CuboCrdtListSyncData, CuboCrdtMutation, CuboCrdtRowId } from '../types'
7
7
  import { areCrdtListIdsEqual, areCrdtListTotalsEqual, checkRowIsSutable } from '../utils'
8
8
 
9
9
  import { CuboCrdtClientDocOrigin } from '../client'
@@ -473,7 +473,7 @@ export class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extr
473
473
  this.subscribesByEntity.get(subscribe.entity as E)?.delete(subscribe_id)
474
474
  }
475
475
 
476
- public getDocument(entity: string, entity_id: number) {
476
+ public getDocument(entity: string, entity_id: CuboCrdtRowId) {
477
477
  return this.documents.get(`${entity}:${entity_id}`)
478
478
  }
479
479
 
@@ -767,7 +767,7 @@ export class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extr
767
767
  // Точечная доставка документа ТОЛЬКО инициирующей подписке (init/upgrade/resubscribe).
768
768
  // Раньше это шло через ensureDocument -> pushDocumentAction('upsert') с полным сканом
769
769
  // всех подписок сущности — на каждый ряд из fetchRows. Теперь — один send на подписку.
770
- private sendDocumentToSubscribe(subscribe: CuboCrdtServerSubscribe, document: CuboCrdtServerDocument, entity_id: number) {
770
+ private sendDocumentToSubscribe(subscribe: CuboCrdtServerSubscribe, document: CuboCrdtServerDocument, entity_id: CuboCrdtRowId) {
771
771
  this.linkSubscribeToDocument(subscribe.id, document.name)
772
772
 
773
773
  this.sendToClient(subscribe.client_id, {
@@ -1054,24 +1054,35 @@ export class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extr
1054
1054
  return true
1055
1055
  }
1056
1056
 
1057
- const rowsById = new Map<number, any>()
1058
- const nextIds: number[] = []
1057
+ // Take the row's id AS IT IS. This was `Number(row.id)` guarded by `Number.isFinite`, which against
1058
+ // uuid keys evaluated NaN for every row and `continue`d past all of them: `nextIds` came out empty,
1059
+ // no document was ever created or linked, and the subscription synced `ids: []` while `totals` —
1060
+ // assigned separately from the fetch — still reported the true count. So the client was told "19
1061
+ // rows exist" and handed none of them, with nothing logged at either end.
1062
+ //
1063
+ // Keyed by String so a uuid and a legacy integer id both index correctly, and so `'42'` and `42`
1064
+ // cannot become two entries for one row.
1065
+ const rowsById = new Map<string, any>()
1066
+ const nextIds: CuboCrdtRowId[] = []
1059
1067
 
1060
1068
  for (const row of result.rows) {
1061
- const id = Number((row as any)?.id)
1062
- if (!Number.isFinite(id) || rowsById.has(id)) {
1069
+ const id = (row as any)?.id
1070
+ if (id === null || id === undefined || id === '' || rowsById.has(String(id))) {
1063
1071
  continue
1064
1072
  }
1065
1073
 
1066
- rowsById.set(id, row)
1074
+ rowsById.set(String(id), row)
1067
1075
  nextIds.push(id)
1068
1076
  }
1069
1077
 
1070
- const previousIds = new Set(subscribe.row_ids)
1071
- const nextIdsSet = new Set(nextIds)
1078
+ // Membership is compared as STRINGS, like the map above: `row_ids` carries whatever was stored on
1079
+ // the previous pass, so a set of raw values would treat `'42'` and `42` as two different rows and
1080
+ // churn documents on every reconcile.
1081
+ const previousIds = new Set(subscribe.row_ids.map((id) => String(id)))
1082
+ const nextIdsSet = new Set(nextIds.map((id) => String(id)))
1072
1083
 
1073
1084
  for (const entity_id of subscribe.row_ids) {
1074
- if (nextIdsSet.has(entity_id)) {
1085
+ if (nextIdsSet.has(String(entity_id))) {
1075
1086
  continue
1076
1087
  }
1077
1088
 
@@ -1081,12 +1092,12 @@ export class CuboCrdtServer<M, A = {}, E extends Extract<keyof M, string> = Extr
1081
1092
  }
1082
1093
 
1083
1094
  for (const entity_id of nextIds) {
1084
- const row = rowsById.get(entity_id)
1095
+ const row = rowsById.get(String(entity_id))
1085
1096
  if (!row) {
1086
1097
  continue
1087
1098
  }
1088
1099
 
1089
- if (previousIds.has(entity_id)) {
1100
+ if (previousIds.has(String(entity_id))) {
1090
1101
  this.linkSubscribeToDocument(subscribe.id, `${subscribe.entity}:${entity_id}`)
1091
1102
  continue
1092
1103
  }
@@ -1,4 +1,4 @@
1
- import type { CuboCrdtListTotals } from '../../types'
1
+ import type { CuboCrdtListTotals, CuboCrdtRowId } from '../../types'
2
2
 
3
3
  export type CuboCrdtServerUnsubscribeDto = {
4
4
  subscribe_id: string
@@ -24,7 +24,7 @@ export type CuboCrdtServerSubscribeDto = {
24
24
  export type CuboCrdtServerSubscribe = {
25
25
  id: string
26
26
  client_id: string
27
- row_ids: number[]
27
+ row_ids: CuboCrdtRowId[]
28
28
  totals: CuboCrdtListTotals
29
29
  revision: number
30
30
  } & Pick<CuboCrdtServerSubscribeDto, 'entity' | 'filters' | 'awareness' | 'paginated' | 'counted'>
@@ -12,8 +12,18 @@ export type CuboCrdtSubscriptionStatus =
12
12
  | 'ready'
13
13
  | 'error'
14
14
 
15
+ /**
16
+ * A row's primary key, as it comes off the wire.
17
+ *
18
+ * Deliberately NOT `number`. Rows are keyed by whatever the database column holds — uuid text in
19
+ * current schemas, integers in older ones — and the CRDT plane only ever compares and indexes ids, it
20
+ * never does arithmetic on them. Typing this as `number` is what let `Number(id)` / `Number.isFinite`
21
+ * creep into both ends of the sync, where every uuid failed the test and the row was silently dropped.
22
+ */
23
+ export type CuboCrdtRowId = string | number
24
+
15
25
  export type CuboCrdtListSyncData = {
16
- ids: number[]
26
+ ids: CuboCrdtRowId[]
17
27
  totals: CuboCrdtListTotals
18
28
  revision: number
19
29
  }
@@ -3,6 +3,7 @@ import { CuboApiEntitiesMap } from '@cuboapp/types'
3
3
  import {
4
4
  CuboCrdtClient,
5
5
  CuboCrdtClientOptions,
6
+ CuboCrdtRowId,
6
7
  CuboCrdtServer,
7
8
  CuboCrdtServerDocumentOrigin,
8
9
  CuboCrdtServerOptions,
@@ -113,8 +114,10 @@ export function checkSubscribeIsSutable(origin: CuboCrdtServerDocumentOrigin, su
113
114
  return noStrategyResolved || strategyOtherResolved || strategyClientResolved || strategySubscribeResolved
114
115
  }
115
116
 
116
- export function areCrdtListIdsEqual(left: number[], right: number[]) {
117
- return left.length === right.length && left.every((id, index) => id === right[index])
117
+ export function areCrdtListIdsEqual(left: CuboCrdtRowId[], right: CuboCrdtRowId[]) {
118
+ // Order matters (the list is ordered), but the id's TYPE does not: comparing as strings keeps a
119
+ // numeric id that made one round trip as text from reading as a changed list and forcing a resync.
120
+ return left.length === right.length && left.every((id, index) => String(id) === String(right[index]))
118
121
  }
119
122
 
120
123
  export function areCrdtListTotalsEqual(