• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 8389227144

22 Mar 2024 10:56AM UTC coverage: 26.087% (-53.9%) from 79.937%
8389227144

push

github

web-flow
refactor: move zod schema to openapi (#487)

2100 of 3363 branches covered (62.44%)

282 of 757 new or added lines in 74 files covered. (37.25%)

14879 existing lines in 182 files now uncovered.

25574 of 98035 relevant lines covered (26.09%)

5.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.64
/apps/nestjs-backend/src/share-db/share-db.service.ts
1
import { Injectable, Logger } from '@nestjs/common';
1✔
2
import { loadPackage } from '@nestjs/common/utils/load-package.util';
1✔
3
import { context as otelContext, trace as otelTrace } from '@opentelemetry/api';
1✔
4
import { FieldOpBuilder, IdPrefix, ViewOpBuilder } from '@teable/core';
1✔
5
import { PrismaService } from '@teable/db-main-prisma';
1✔
6
import { noop } from 'lodash';
1✔
7
import { ClsService } from 'nestjs-cls';
1✔
8
import type { CreateOp, DeleteOp, EditOp } from 'sharedb';
1✔
9
import ShareDBClass from 'sharedb';
1✔
10
import { CacheConfig, ICacheConfig } from '../configs/cache.config';
1✔
11
import { EventEmitterService } from '../event-emitter/event-emitter.service';
1✔
12
import type { IClsStore } from '../types/cls';
1✔
13
import { Timing } from '../utils/timing';
1✔
14
import { authMiddleware } from './auth.middleware';
1✔
15
import { derivateMiddleware } from './derivate.middleware';
1✔
16
import type { IRawOpMap } from './interface';
1✔
17
import { ShareDbPermissionService } from './share-db-permission.service';
1✔
18
import { ShareDbAdapter } from './share-db.adapter';
1✔
19
import { WsDerivateService } from './ws-derivate.service';
1✔
20

1✔
21
@Injectable()
1✔
22
export class ShareDbService extends ShareDBClass {
1✔
23
  private logger = new Logger(ShareDbService.name);
150✔
24

150✔
25
  constructor(
150✔
26
    readonly shareDbAdapter: ShareDbAdapter,
150✔
27
    private readonly eventEmitterService: EventEmitterService,
150✔
28
    private readonly prismaService: PrismaService,
150✔
29
    private readonly cls: ClsService<IClsStore>,
150✔
30
    private readonly wsDerivateService: WsDerivateService,
150✔
31
    private readonly shareDbPermissionService: ShareDbPermissionService,
150✔
32
    @CacheConfig() private readonly cacheConfig: ICacheConfig
150✔
33
  ) {
150✔
34
    super({
150✔
35
      presence: true,
150✔
36
      doNotForwardSendPresenceErrorsToClient: true,
150✔
37
      db: shareDbAdapter,
150✔
38
    });
150✔
39

150✔
40
    const { provider, redis } = this.cacheConfig;
150✔
41

150✔
42
    if (provider === 'redis') {
150!
43
      const redisPubsub = loadPackage('sharedb-redis-pubsub', ShareDbService.name, () =>
×
44
        require('sharedb-redis-pubsub')
×
45
      )({ url: redis.uri });
×
46

×
47
      this.logger.log(`> Detected Redis cache; enabled the Redis pub/sub adapter for ShareDB.`);
×
48
      this.pubsub = redisPubsub;
×
49
    }
×
50

150✔
51
    // auth
150✔
52
    authMiddleware(this, this.shareDbPermissionService);
150✔
53
    derivateMiddleware(this, this.cls, this.wsDerivateService);
150✔
54

150✔
55
    this.use('submit', this.onSubmit);
150✔
56

150✔
57
    // broadcast raw op events to client
150✔
58
    this.prismaService.bindAfterTransaction(() => {
150✔
UNCOV
59
      const rawOpMaps = this.cls.get('tx.rawOpMaps');
×
UNCOV
60
      const stashOpMap = this.cls.get('tx.stashOpMap');
×
UNCOV
61
      this.cls.set('tx.rawOpMaps', undefined);
×
UNCOV
62
      this.cls.set('tx.stashOpMap', undefined);
×
UNCOV
63

×
UNCOV
64
      const ops: IRawOpMap[] = [];
×
UNCOV
65
      if (stashOpMap) {
×
UNCOV
66
        ops.push(stashOpMap);
×
UNCOV
67
      }
×
UNCOV
68
      if (rawOpMaps?.length) {
×
UNCOV
69
        ops.push(...rawOpMaps);
×
UNCOV
70
      }
×
UNCOV
71

×
UNCOV
72
      if (ops.length) {
×
UNCOV
73
        this.publishOpsMap(rawOpMaps);
×
UNCOV
74
        this.eventEmitterService.ops2Event(ops);
×
UNCOV
75
      }
×
UNCOV
76
    });
×
77
  }
150✔
78

150✔
79
  getConnection() {
150✔
80
    const connection = this.connect();
×
81
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
×
82
    connection.agent!.custom.isBackend = true;
×
83
    return connection;
×
84
  }
×
85

150✔
86
  @Timing()
150✔
87
  publishOpsMap(rawOpMaps: IRawOpMap[] | undefined) {
150✔
UNCOV
88
    if (!rawOpMaps?.length) {
×
UNCOV
89
      return;
×
UNCOV
90
    }
×
UNCOV
91
    for (const rawOpMap of rawOpMaps) {
×
UNCOV
92
      for (const collection in rawOpMap) {
×
UNCOV
93
        const data = rawOpMap[collection];
×
UNCOV
94
        for (const docId in data) {
×
UNCOV
95
          const rawOp = data[docId] as EditOp | CreateOp | DeleteOp;
×
UNCOV
96
          const channels = [collection, `${collection}.${docId}`];
×
UNCOV
97
          rawOp.c = collection;
×
UNCOV
98
          rawOp.d = docId;
×
UNCOV
99
          this.pubsub.publish(channels, rawOp, noop);
×
UNCOV
100

×
UNCOV
101
          if (this.shouldPublishAction(rawOp)) {
×
UNCOV
102
            const tableId = collection.split('_')[1];
×
UNCOV
103
            this.publishRelatedChannels(tableId, rawOp);
×
UNCOV
104
          }
×
UNCOV
105
        }
×
UNCOV
106
      }
×
UNCOV
107
    }
×
UNCOV
108
  }
×
109

150✔
110
  private shouldPublishAction(rawOp: EditOp | CreateOp | DeleteOp) {
150✔
UNCOV
111
    const viewKeys = ['filter', 'sort', 'group', 'lastModifiedTime'];
×
UNCOV
112
    const fieldKeys = ['options'];
×
UNCOV
113
    return rawOp.op?.some(
×
UNCOV
114
      (op) =>
×
UNCOV
115
        viewKeys.includes(ViewOpBuilder.editor.setViewProperty.detect(op)?.key as string) ||
×
UNCOV
116
        fieldKeys.includes(FieldOpBuilder.editor.setFieldProperty.detect(op)?.key as string)
×
UNCOV
117
    );
×
UNCOV
118
  }
×
119

150✔
120
  /**
150✔
121
   * this is for some special scenarios like manual sort
150✔
122
   * which only send view ops but update record too
150✔
123
   */
150✔
124
  private publishRelatedChannels(tableId: string, rawOp: EditOp | CreateOp | DeleteOp) {
150✔
UNCOV
125
    this.pubsub.publish([`${IdPrefix.Record}_${tableId}`], rawOp, noop);
×
UNCOV
126
    this.pubsub.publish([`${IdPrefix.Field}_${tableId}`], rawOp, noop);
×
UNCOV
127
  }
×
128

150✔
129
  private onSubmit = (
150✔
UNCOV
130
    context: ShareDBClass.middleware.SubmitContext,
×
UNCOV
131
    next: (err?: unknown) => void
×
UNCOV
132
  ) => {
×
UNCOV
133
    const tracer = otelTrace.getTracer('default');
×
UNCOV
134
    const currentSpan = tracer.startSpan('submitOp');
×
UNCOV
135

×
UNCOV
136
    // console.log('onSubmit start');
×
UNCOV
137

×
UNCOV
138
    otelContext.with(otelTrace.setSpan(otelContext.active(), currentSpan), () => {
×
UNCOV
139
      const [docType] = context.collection.split('_');
×
UNCOV
140

×
UNCOV
141
      if (docType !== IdPrefix.Record || !context.op.op) {
×
142
        return next(new Error('only record op can be committed'));
×
143
      }
×
UNCOV
144
      next();
×
UNCOV
145
    });
×
UNCOV
146

×
UNCOV
147
    // console.log('onSubmit end');
×
UNCOV
148
  };
×
149
}
150✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc