@servicenow/sdk-build-core 4.2.0 → 4.4.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 (83) hide show
  1. package/dist/compiler.d.ts +14 -2
  2. package/dist/compiler.js +120 -8
  3. package/dist/compiler.js.map +1 -1
  4. package/dist/compression.d.ts +2 -2
  5. package/dist/compression.js +4 -1
  6. package/dist/compression.js.map +1 -1
  7. package/dist/now-config-dependencies.d.ts +2 -1
  8. package/dist/now-config.d.ts +10 -5
  9. package/dist/now-config.js +6 -5
  10. package/dist/now-config.js.map +1 -1
  11. package/dist/plugins/cache.d.ts +8 -13
  12. package/dist/plugins/cache.js +2 -26
  13. package/dist/plugins/cache.js.map +1 -1
  14. package/dist/plugins/data-shape.d.ts +24 -33
  15. package/dist/plugins/data-shape.js +83 -73
  16. package/dist/plugins/data-shape.js.map +1 -1
  17. package/dist/plugins/plugin.d.ts +43 -1
  18. package/dist/plugins/plugin.js +49 -5
  19. package/dist/plugins/plugin.js.map +1 -1
  20. package/dist/plugins/shape.d.ts +19 -11
  21. package/dist/plugins/shape.js +61 -23
  22. package/dist/plugins/shape.js.map +1 -1
  23. package/dist/taxonomy.js +53 -1
  24. package/dist/taxonomy.js.map +1 -1
  25. package/dist/telemetry/clients/abstract-client.d.ts +25 -0
  26. package/dist/telemetry/clients/abstract-client.js +55 -0
  27. package/dist/telemetry/clients/abstract-client.js.map +1 -0
  28. package/dist/telemetry/clients/browser-client.d.ts +20 -0
  29. package/dist/telemetry/clients/browser-client.js +136 -0
  30. package/dist/telemetry/clients/browser-client.js.map +1 -0
  31. package/dist/telemetry/clients/node-client.d.ts +15 -0
  32. package/dist/telemetry/clients/node-client.js +159 -0
  33. package/dist/telemetry/clients/node-client.js.map +1 -0
  34. package/dist/telemetry/clients/noop-client.d.ts +10 -0
  35. package/dist/telemetry/clients/noop-client.js +18 -0
  36. package/dist/telemetry/clients/noop-client.js.map +1 -0
  37. package/dist/telemetry/clients/util.d.ts +11 -0
  38. package/dist/telemetry/clients/util.js +34 -0
  39. package/dist/telemetry/clients/util.js.map +1 -0
  40. package/dist/telemetry/config.d.ts +2 -0
  41. package/dist/telemetry/config.js +28 -0
  42. package/dist/telemetry/config.js.map +1 -0
  43. package/dist/telemetry/factory.d.ts +13 -0
  44. package/dist/telemetry/factory.js +29 -0
  45. package/dist/telemetry/factory.js.map +1 -0
  46. package/dist/telemetry/index.d.ts +2 -25
  47. package/dist/telemetry/index.js +3 -15
  48. package/dist/telemetry/index.js.map +1 -1
  49. package/dist/telemetry/types.d.ts +55 -0
  50. package/dist/telemetry/types.js +12 -0
  51. package/dist/telemetry/types.js.map +1 -0
  52. package/dist/typescript.d.ts +10 -0
  53. package/dist/typescript.js +18 -0
  54. package/dist/typescript.js.map +1 -1
  55. package/dist/util/delete-multiple.d.ts +5 -0
  56. package/dist/util/delete-multiple.js +30 -0
  57. package/dist/util/delete-multiple.js.map +1 -0
  58. package/dist/util/index.d.ts +1 -0
  59. package/dist/util/index.js +1 -0
  60. package/dist/util/index.js.map +1 -1
  61. package/now.config.schema.json +40 -5
  62. package/package.json +13 -9
  63. package/src/compiler.ts +140 -7
  64. package/src/compression.ts +2 -2
  65. package/src/now-config-dependencies.ts +2 -1
  66. package/src/now-config.ts +8 -6
  67. package/src/plugins/cache.ts +5 -27
  68. package/src/plugins/data-shape.ts +95 -84
  69. package/src/plugins/plugin.ts +116 -9
  70. package/src/plugins/shape.ts +64 -30
  71. package/src/taxonomy.ts +53 -1
  72. package/src/telemetry/clients/abstract-client.ts +63 -0
  73. package/src/telemetry/clients/browser-client.ts +160 -0
  74. package/src/telemetry/clients/node-client.ts +151 -0
  75. package/src/telemetry/clients/noop-client.ts +15 -0
  76. package/src/telemetry/clients/util.ts +33 -0
  77. package/src/telemetry/config.ts +12 -0
  78. package/src/telemetry/factory.ts +34 -0
  79. package/src/telemetry/index.ts +2 -27
  80. package/src/telemetry/types.ts +61 -0
  81. package/src/typescript.ts +17 -0
  82. package/src/util/delete-multiple.ts +35 -0
  83. package/src/util/index.ts +1 -0
@@ -0,0 +1,160 @@
1
+ import type { TelemetryEvent, TelemetryEventData, Datapoint, HeartbeatBody } from '../types'
2
+ import { NETWORK_TIMEOUT_MS, DatapointType, type InstanceSettings } from '../types'
3
+ import { AbstractAppSeeClient } from './abstract-client'
4
+
5
+ export class BrowserTelemetryClient extends AbstractAppSeeClient {
6
+ constructor(
7
+ config: InstanceSettings,
8
+ telemetryAttributes?: {
9
+ sdkVersion?: string
10
+ clientName?: 'ide' | string
11
+ hostname?: string
12
+ ideVersion?: string
13
+ }
14
+ ) {
15
+ const { ideVersion, ...baseAttributes } = telemetryAttributes ?? {}
16
+ super(config, {
17
+ ...baseAttributes,
18
+ clientName: telemetryAttributes?.clientName ?? 'ide',
19
+ })
20
+
21
+ this.ideVersion = ideVersion
22
+ }
23
+
24
+ private readonly ideVersion: string | undefined
25
+ private userId?: string
26
+
27
+ private async getUserHash() {
28
+ const userUniqueId = await this.getUserIdentifier()
29
+ const bytes = new TextEncoder().encode(userUniqueId)
30
+ const byteHash = await crypto.subtle.digest('SHA-256', bytes)
31
+ const hashArray = Array.from(new Uint8Array(byteHash))
32
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
33
+ }
34
+
35
+ private async getUserIdentifier(): Promise<string> {
36
+ if (typeof window === 'undefined') {
37
+ return 'unknown'
38
+ }
39
+
40
+ const storageKey = 'sdk_telemetry_user_id'
41
+ try {
42
+ let userId = localStorage.getItem(storageKey)
43
+
44
+ if (!userId) {
45
+ userId = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`
46
+ localStorage.setItem(storageKey, userId)
47
+ }
48
+
49
+ return userId
50
+ } catch {
51
+ return `temp_${Math.random().toString(36).substring(2, 15)}`
52
+ }
53
+ }
54
+
55
+ private generateHexString(): string {
56
+ return crypto.randomUUID()
57
+ }
58
+
59
+ override async doStart(): Promise<void> {
60
+ try {
61
+ if (this.session) {
62
+ return
63
+ }
64
+
65
+ this.userId = await this.getUserHash()
66
+
67
+ const tabId = this.generateHexString()
68
+ const body = {
69
+ RequestId: this.generateHexString(),
70
+ TabId: tabId,
71
+ SystemLocale: navigator.language ?? 'unknown',
72
+ AppUserId: this.userId,
73
+ ScreenHeight: typeof screen !== 'undefined' ? screen.height : 0,
74
+ ScreenWidth: typeof screen !== 'undefined' ? screen.width : 0,
75
+ ClientTime: new Date().toISOString(),
76
+ TrackingLevel: 'Full',
77
+ }
78
+
79
+ const response = await fetch(new URL('/web/config', this.config.BaseUrl), {
80
+ method: 'POST',
81
+ headers: this.getHeaders(),
82
+ body: JSON.stringify(body),
83
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
84
+ })
85
+
86
+ const bodyResponse = await response.json()
87
+ if (response.ok) {
88
+ this.session = { ...bodyResponse, TabId: tabId }
89
+ } else {
90
+ throw new Error(`Failed to start session with status ${response.status}: ${bodyResponse.Error}`)
91
+ }
92
+ } catch (_error) {}
93
+ }
94
+
95
+ override doSendEvent(events: TelemetryEvent | TelemetryEvent[]): void {
96
+ try {
97
+ if (!this.session) {
98
+ return
99
+ }
100
+
101
+ const defaultValues = this.getDefaultDataValues()
102
+ const eventData = Array.isArray(events) ? events : [events]
103
+ const now = Date.now()
104
+ const hostname = this.hostname ? { hostname: this.hostname } : {}
105
+
106
+ const dataPoints = eventData.map<Datapoint>((d) => ({
107
+ t: DatapointType.Event,
108
+ d: now,
109
+ n: d.name,
110
+ p: { scopeId: d.appInfo?.scopeId, ...hostname, ...defaultValues, ...d.data },
111
+ }))
112
+
113
+ const userProps = { hostname } //does CI make sense for browser environment ??
114
+
115
+ dataPoints.push({
116
+ t: DatapointType.User,
117
+ d: now,
118
+ n: this.userId!,
119
+ p: userProps,
120
+ })
121
+
122
+ const body: HeartbeatBody = {
123
+ SessionId: this.session.SessionId,
124
+ DataPoints: dataPoints,
125
+ TabId: this.session.TabId || '0',
126
+ ClientTime: new Date().toISOString(),
127
+ ConfigReceivedTime: new Date().toISOString(),
128
+ }
129
+
130
+ fetch(new URL('/web/heartbeat', this.config.BaseUrl), {
131
+ method: 'POST',
132
+ headers: this.getHeaders(),
133
+ body: JSON.stringify(body),
134
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
135
+ }).catch(() => {})
136
+ } catch (_error) {}
137
+ }
138
+
139
+ private getHeaders() {
140
+ return {
141
+ APIKey: this.config.APIKey,
142
+ APIAuth: this.config.APIAuth,
143
+ BrowserId: '0',
144
+ ClientId: this.session?.ClientId || '0',
145
+ Version: this.sdkVersion || 'unknown',
146
+ 'Content-Type': 'application/json',
147
+ }
148
+ }
149
+
150
+ private getDefaultDataValues() {
151
+ const defaultValues: TelemetryEventData = {
152
+ browser: navigator.userAgent || 'unknown',
153
+ version: this.sdkVersion || 'unknown',
154
+ clientName: this.clientName,
155
+ ideVersion: this.ideVersion || 'unknown',
156
+ }
157
+
158
+ return defaultValues
159
+ }
160
+ }
@@ -0,0 +1,151 @@
1
+ import * as os from 'os'
2
+ import ciInfo from 'ci-info'
3
+ import { NETWORK_TIMEOUT_MS, DatapointType } from '../types'
4
+ import type { TelemetryEvent, TelemetryEventData, Datapoint, HeartbeatBody, InstanceSettings } from '../types'
5
+ import { AbstractAppSeeClient } from './abstract-client'
6
+
7
+ function generateHexString() {
8
+ // biome-ignore lint/style/noRestrictedImports: <explanation>
9
+ const crypto = require('node:crypto')
10
+ return crypto.randomBytes(16).toString('hex')
11
+ }
12
+
13
+ async function getUserHash() {
14
+ const user = `${os.userInfo().username}:${os.hostname()}`
15
+ if (!user) {
16
+ return 'unknown'
17
+ }
18
+
19
+ const bytes = new TextEncoder().encode(user)
20
+ const byteHash = await crypto.subtle.digest('SHA-256', bytes)
21
+ return Buffer.from(byteHash).toString('hex')
22
+ }
23
+
24
+ export class NodeTelemetryClient extends AbstractAppSeeClient {
25
+ constructor(
26
+ config: InstanceSettings,
27
+ telemetryAttributes?: { sdkVersion?: string; clientName?: 'cli' | string; hostname?: string }
28
+ ) {
29
+ super(config, {
30
+ ...(telemetryAttributes ?? {}),
31
+ clientName: telemetryAttributes?.clientName ?? 'cli',
32
+ })
33
+ }
34
+
35
+ private userId?: string
36
+
37
+ override async doStart(): Promise<void> {
38
+ try {
39
+ if (this.session) {
40
+ return
41
+ }
42
+
43
+ this.userId = await getUserHash()
44
+
45
+ const tabId = generateHexString()
46
+ const body = {
47
+ RequestId: generateHexString(),
48
+ TabId: tabId,
49
+ SystemLocale: (process.env['LANG'] || 'unknown').split('.')[0],
50
+ AppUserId: this.userId,
51
+ ScreenHeight: 0,
52
+ ScreenWidth: 0,
53
+ ClientTime: new Date().toISOString(),
54
+ TrackingLevel: 'Full',
55
+ }
56
+
57
+ const response = await fetch(new URL('/web/config', this.config.BaseUrl), {
58
+ method: 'POST',
59
+ headers: this.getHeaders(),
60
+ body: JSON.stringify(body),
61
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
62
+ })
63
+
64
+ const bodyResponse = await response.json()
65
+ if (response.ok) {
66
+ this.session = { ...bodyResponse, TabId: tabId }
67
+ } else {
68
+ throw new Error(`Failed to start session with status ${response.status}: ${bodyResponse.Error}`)
69
+ }
70
+ } catch (_error) {
71
+ //ignore
72
+ }
73
+ }
74
+
75
+ override doSendEvent(events: TelemetryEvent | TelemetryEvent[]): void {
76
+ try {
77
+ //Check if we have a session
78
+ if (!this.session) {
79
+ return
80
+ }
81
+
82
+ const defaultValues = this.getDefaultDataValues()
83
+ const eventData = Array.isArray(events) ? events : [events]
84
+ const now = Date.now()
85
+ const hostname = this.hostname ? { hostname: this.hostname } : {}
86
+
87
+ const dataPoints = eventData.map<Datapoint>((d) => ({
88
+ t: DatapointType.Event,
89
+ d: now,
90
+ n: d.name,
91
+ p: { scopeId: d.appInfo?.scopeId, ...hostname, ...defaultValues, ...d.data },
92
+ }))
93
+
94
+ const userProps = this.applyCITelemetry({ ...hostname })
95
+
96
+ dataPoints.push({
97
+ t: DatapointType.User,
98
+ d: now,
99
+ n: this.userId!,
100
+ p: userProps,
101
+ })
102
+
103
+ const body: HeartbeatBody = {
104
+ SessionId: this.session.SessionId,
105
+ DataPoints: dataPoints,
106
+ TabId: this.session.TabId || '0',
107
+ ClientTime: new Date().toISOString(),
108
+ ConfigReceivedTime: new Date().toISOString(),
109
+ }
110
+
111
+ fetch(new URL('/web/heartbeat', this.config.BaseUrl), {
112
+ method: 'POST',
113
+ headers: this.getHeaders(),
114
+ body: JSON.stringify(body),
115
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
116
+ }).catch(() => {})
117
+ } catch (_error) {
118
+ //ignore errors from telemetry
119
+ }
120
+ }
121
+
122
+ private getHeaders() {
123
+ return {
124
+ APIKey: this.config.APIKey,
125
+ APIAuth: this.config.APIAuth,
126
+ BrowserId: '0',
127
+ ClientId: this.session?.ClientId || '0',
128
+ Version: this.sdkVersion || 'unknown',
129
+ 'Content-Type': 'application/json',
130
+ }
131
+ }
132
+
133
+ private getDefaultDataValues() {
134
+ const defaultValues: TelemetryEventData = {
135
+ os: os.type(),
136
+ version: this.sdkVersion || 'unknown',
137
+ nodeVersion: process.version,
138
+ clientName: this.clientName,
139
+ }
140
+
141
+ return this.applyCITelemetry(defaultValues)
142
+ }
143
+
144
+ private applyCITelemetry(data: TelemetryEventData) {
145
+ if (ciInfo.isCI) {
146
+ return { ...data, ci: true, ciName: ciInfo.name, ciPR: ciInfo.isPR }
147
+ }
148
+
149
+ return data
150
+ }
151
+ }
@@ -0,0 +1,15 @@
1
+ import type { Telemetry, TimerMetric } from '../types'
2
+
3
+ /**
4
+ * This is a no-op telemetry implementation that does nothing.
5
+ */
6
+ export class NoOpTelemetry implements Telemetry {
7
+ startTimerEvent(): TimerMetric {
8
+ return {
9
+ end: () => ({ name: '', data: {} }),
10
+ }
11
+ }
12
+ start = () => {}
13
+ sendEvent = () => {}
14
+ error = () => {}
15
+ }
@@ -0,0 +1,33 @@
1
+ import type { TelemetryEvent, TimerMetric } from '../types'
2
+
3
+ export class AppSeeTimerMetric implements TimerMetric {
4
+ private readonly startTime: number
5
+
6
+ constructor(private readonly name: TelemetryEvent['name']) {
7
+ this.startTime = Date.now()
8
+ }
9
+
10
+ getDurationMs() {
11
+ const now = Date.now()
12
+ return {
13
+ duration: now - this.startTime,
14
+ now,
15
+ }
16
+ }
17
+
18
+ end(eventData?: Pick<TelemetryEvent, 'appInfo' | 'data'>): TelemetryEvent {
19
+ const durationMs = Date.now() - this.startTime
20
+ /**
21
+ * Create a an event containing the duration and all checkpoint durations
22
+ */
23
+
24
+ return {
25
+ name: this.name,
26
+ appInfo: eventData?.appInfo || {},
27
+ data: {
28
+ ...eventData?.data,
29
+ durationMs,
30
+ },
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,12 @@
1
+ import type { InstanceSettings } from './types'
2
+
3
+ declare const __APPSEE_API_KEY__: string
4
+ declare const __APPSEE_AUTH_KEY__: string
5
+ declare const __APPSEE_URL__: string
6
+
7
+ //will be populated by esbuild during the release build
8
+ export const getAppSeeConfig = (): InstanceSettings => ({
9
+ APIKey: __APPSEE_API_KEY__,
10
+ APIAuth: __APPSEE_AUTH_KEY__,
11
+ BaseUrl: __APPSEE_URL__,
12
+ })
@@ -0,0 +1,34 @@
1
+ /** biome-ignore-all lint/complexity/noStaticOnlyClass: <explanation> */
2
+ import type { Telemetry } from './types'
3
+ import { NoOpTelemetry } from './clients/noop-client'
4
+ import { NodeTelemetryClient } from './clients/node-client'
5
+ import { BrowserTelemetryClient } from './clients/browser-client'
6
+ import { getAppSeeConfig } from './config'
7
+
8
+ export class TelemetryFactory {
9
+ static create(options?: {
10
+ type?: 'node' | 'browser'
11
+ attributes?: { sdkVersion?: string; clientName?: string; hostname?: string; ideVersion?: string }
12
+ }): Telemetry {
13
+ const config = getAppSeeConfig()
14
+
15
+ if (
16
+ process.env['NODE_ENV'] === 'test' ||
17
+ !options ||
18
+ !options.type ||
19
+ !config.APIKey ||
20
+ !config.APIAuth ||
21
+ !config.BaseUrl
22
+ ) {
23
+ return new NoOpTelemetry()
24
+ }
25
+
26
+ if (options.type === 'node') {
27
+ return new NodeTelemetryClient(config, options.attributes)
28
+ } else if (options.type === 'browser') {
29
+ return new BrowserTelemetryClient(config, options.attributes)
30
+ }
31
+
32
+ throw new Error('Invalid telemetry type.')
33
+ }
34
+ }
@@ -1,27 +1,2 @@
1
- export interface Telemetry {
2
- sendEvent(eventData: TelemetryEvent | TelemetryEvent[]): Promise<void>
3
- error(event: string, error: unknown, data?: TelemetryEventData): Promise<void>
4
- startTimerEvent(name: TelemetryEvent['name']): TimerMetric
5
- }
6
-
7
- export interface TimerMetric {
8
- end(eventData?: Pick<TelemetryEvent, 'appInfo' | 'data'>): TelemetryEvent
9
- }
10
-
11
- export type TelemetryEventData = Record<string, unknown>
12
-
13
- export type TelemetryEvent = { name: string; appInfo?: { scopeId?: string }; data?: TelemetryEventData }
14
-
15
- /**
16
- * This is a no-op telemetry implementation that does nothing.
17
- */
18
- export class NoOpTelemetry implements Telemetry {
19
- startTimerEvent(): TimerMetric {
20
- return {
21
- end: () => ({ name: '', data: {} }),
22
- }
23
- }
24
- start = () => Promise.resolve()
25
- sendEvent = () => Promise.resolve()
26
- error = () => Promise.resolve()
27
- }
1
+ export type { Telemetry, TelemetryEvent } from './types'
2
+ export { TelemetryFactory } from './factory'
@@ -0,0 +1,61 @@
1
+ export const NETWORK_TIMEOUT_MS = 2000
2
+
3
+ export type InstanceSettings = {
4
+ APIKey: string
5
+ APIAuth: string
6
+ BaseUrl: string
7
+ }
8
+
9
+ export type Session = {
10
+ SessionId: string
11
+ ClientId: string
12
+ TabId: string
13
+ }
14
+
15
+ export enum DatapointType {
16
+ Page = 0,
17
+ Event = 1,
18
+ User = 2,
19
+ PerformanceTrace = 3,
20
+ }
21
+
22
+ export type Datapoint = {
23
+ t: DatapointType
24
+
25
+ /**
26
+ * Epoch time in milliseconds
27
+ */
28
+ d: number
29
+
30
+ /**
31
+ * Event name
32
+ */
33
+ n: string
34
+
35
+ /**
36
+ * Event properties
37
+ */
38
+ p?: Record<string, unknown>
39
+ }
40
+
41
+ export type HeartbeatBody = {
42
+ SessionId: string
43
+ DataPoints: Datapoint[]
44
+ TabId: string
45
+ ClientTime: string
46
+ ConfigReceivedTime: string
47
+ }
48
+
49
+ export interface Telemetry {
50
+ sendEvent(eventData: TelemetryEvent | TelemetryEvent[]): void
51
+ error(event: string, error: unknown, data?: TelemetryEventData): void
52
+ startTimerEvent(name: TelemetryEvent['name']): TimerMetric
53
+ }
54
+
55
+ export interface TimerMetric {
56
+ end(eventData?: Pick<TelemetryEvent, 'appInfo' | 'data'>): TelemetryEvent
57
+ }
58
+
59
+ export type TelemetryEventData = Record<string, unknown>
60
+
61
+ export type TelemetryEvent = { name: string; appInfo?: { scopeId?: string }; data?: TelemetryEventData }
package/src/typescript.ts CHANGED
@@ -31,6 +31,23 @@ import { ts as tsc } from 'ts-morph'
31
31
 
32
32
  export { ts, tsc }
33
33
 
34
+ /**
35
+ * Checks if a value is a valid ts-morph Node that can be operated on.
36
+ *
37
+ * Unlike `ts.Node.isNode()`, this function safely handles nodes that have been
38
+ * removed or forgotten from the AST, returning `false` instead of throwing.
39
+ *
40
+ * @param value - The value to check
41
+ * @returns `true` if the value is a valid, usable Node; `false` otherwise
42
+ */
43
+ export function isValidNode(value: unknown): value is ts.Node {
44
+ try {
45
+ return ts.Node.isNode(value)
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+
34
51
  export function getValueDeclaration(node: ts.Node) {
35
52
  const symbol = node.getSymbol()
36
53
  if (!symbol) {
@@ -0,0 +1,35 @@
1
+ import { type Context, Database } from '../plugins'
2
+
3
+ export async function deleteMultipleDiff(
4
+ existing: Database,
5
+ incoming: Database,
6
+ descendants: Database,
7
+ { factory }: Pick<Context, 'factory'>
8
+ ) {
9
+ if (incoming.query().length === 0) {
10
+ // No changes for this record so we don't search descendents for absent deletes.
11
+ return {
12
+ success: true,
13
+ value: new Database(),
14
+ }
15
+ }
16
+
17
+ const changeDatabase = existing.compare(incoming)
18
+ const markForRemoval = descendants.query().filter((descendent) => !incoming.resolve(descendent.getId()))
19
+ for (const record of markForRemoval) {
20
+ const deleteRecord = await factory.createRecord({
21
+ source: record.getSource(),
22
+ table: record.getTable(),
23
+ explicitId: record.getId(),
24
+ properties: record.properties(),
25
+ action: 'DELETE',
26
+ })
27
+
28
+ changeDatabase.insert(deleteRecord)
29
+ }
30
+
31
+ return {
32
+ success: true,
33
+ value: changeDatabase.hasChanges() ? new Database(changeDatabase.query()) : new Database(),
34
+ }
35
+ }
package/src/util/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './is-sn-scope'
2
2
  export * from './get-file-type'
3
+ export * from './delete-multiple'