dd-trace 4.18.0 → 4.19.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.
- package/LICENSE-3rdparty.csv +2 -2
- package/README.md +3 -3
- package/ext/kinds.d.ts +1 -0
- package/ext/kinds.js +2 -1
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +6 -1
- package/package.json +6 -6
- package/packages/datadog-core/src/storage/async_resource.js +1 -1
- package/packages/datadog-esbuild/index.js +1 -20
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/restify.js +14 -1
- package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
- package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
- package/packages/dd-trace/src/appsec/channels.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
- package/packages/dd-trace/src/appsec/recommended.json +272 -48
- package/packages/dd-trace/src/appsec/reporter.js +31 -34
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +17 -7
- package/packages/dd-trace/src/config.js +6 -4
- package/packages/dd-trace/src/datastreams/processor.js +60 -15
- package/packages/dd-trace/src/format.js +6 -1
- package/packages/dd-trace/src/iitm.js +1 -1
- package/packages/dd-trace/src/log/channels.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +95 -2
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
- package/packages/dd-trace/src/opentracing/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span_context.js +5 -2
- package/packages/dd-trace/src/plugin_manager.js +1 -1
- package/packages/dd-trace/src/plugins/database.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +6 -19
- package/packages/dd-trace/src/plugins/util/git.js +2 -1
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
- package/packages/dd-trace/src/plugins/util/url.js +26 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
- package/packages/dd-trace/src/profiling/config.js +19 -2
- package/packages/dd-trace/src/profiling/profilers/events.js +161 -0
- package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +61 -29
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/span_processor.js +4 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/tracer.js +4 -2
- package/packages/diagnostics_channel/index.js +0 -3
- package/packages/diagnostics_channel/src/index.js +0 -121
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const { performance, constants, PerformanceObserver } = require('node:perf_hooks')
|
|
2
|
+
const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
|
|
3
|
+
const semver = require('semver')
|
|
4
|
+
const { Function, Label, Line, Location, Profile, Sample, StringTable, ValueType } = require('pprof-format')
|
|
5
|
+
const pprof = require('@datadog/pprof/')
|
|
6
|
+
|
|
7
|
+
// Format of perf_hooks events changed with Node 16, we need to be mindful of it.
|
|
8
|
+
const node16 = semver.gte(process.version, '16.0.0')
|
|
9
|
+
|
|
10
|
+
// perf_hooks uses millis, with fractional part representing nanos. We emit nanos into the pprof file.
|
|
11
|
+
const MS_TO_NS = 1000000
|
|
12
|
+
|
|
13
|
+
// While this is an "events profiler", meaning it emits a pprof file based on events observed as
|
|
14
|
+
// perf_hooks events, the emitted pprof file uses the type "timeline".
|
|
15
|
+
const pprofValueType = 'timeline'
|
|
16
|
+
const pprofValueUnit = 'nanoseconds'
|
|
17
|
+
const threadName = `${threadNamePrefix} GC`
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This class generates pprof files with timeline events sourced from Node.js
|
|
21
|
+
* performance measurement APIs.
|
|
22
|
+
*/
|
|
23
|
+
class EventsProfiler {
|
|
24
|
+
constructor (options = {}) {
|
|
25
|
+
this.type = 'events'
|
|
26
|
+
this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
|
|
27
|
+
this._observer = undefined
|
|
28
|
+
this.entries = []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
start () {
|
|
32
|
+
function add (items) {
|
|
33
|
+
this.entries.push(...items.getEntries())
|
|
34
|
+
}
|
|
35
|
+
if (!this._observer) {
|
|
36
|
+
this._observer = new PerformanceObserver(add.bind(this))
|
|
37
|
+
}
|
|
38
|
+
// Currently only support GC
|
|
39
|
+
this._observer.observe({ type: 'gc' })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop () {
|
|
43
|
+
if (this._observer) {
|
|
44
|
+
this._observer.disconnect()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
profile () {
|
|
49
|
+
const stringTable = new StringTable()
|
|
50
|
+
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP)
|
|
51
|
+
const kindLabelKey = stringTable.dedup('gc type')
|
|
52
|
+
const reasonLabelKey = stringTable.dedup('gc reason')
|
|
53
|
+
const kindLabels = []
|
|
54
|
+
const reasonLabels = []
|
|
55
|
+
const locations = []
|
|
56
|
+
const functions = []
|
|
57
|
+
const locationsPerKind = []
|
|
58
|
+
const flagObj = {}
|
|
59
|
+
|
|
60
|
+
function labelFromStr (key, valStr) {
|
|
61
|
+
return new Label({ key, str: stringTable.dedup(valStr) })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function labelFromStrStr (keyStr, valStr) {
|
|
65
|
+
return labelFromStr(stringTable.dedup(keyStr), valStr)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create labels for all GC performance flags and kinds of GC
|
|
69
|
+
for (const [key, value] of Object.entries(constants)) {
|
|
70
|
+
if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
|
|
71
|
+
flagObj[key.substring(26).toLowerCase()] = value
|
|
72
|
+
} else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
|
|
73
|
+
// It's a constant for a kind of GC
|
|
74
|
+
const kind = key.substring(20).toLowerCase()
|
|
75
|
+
kindLabels[value] = labelFromStr(kindLabelKey, kind)
|
|
76
|
+
// Construct a single-frame "location" too
|
|
77
|
+
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup(`${kind} GC`) })
|
|
78
|
+
functions.push(fn)
|
|
79
|
+
const line = new Line({ functionId: fn.id })
|
|
80
|
+
const location = new Location({ id: locations.length + 1, line: [line] })
|
|
81
|
+
locations.push(location)
|
|
82
|
+
locationsPerKind[value] = [location.id]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const gcEventLabel = labelFromStrStr('event', 'gc')
|
|
87
|
+
const threadLabel = labelFromStrStr(THREAD_NAME, threadName)
|
|
88
|
+
|
|
89
|
+
function getReasonLabel (flags) {
|
|
90
|
+
if (flags === 0) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
let reasonLabel = reasonLabels[flags]
|
|
94
|
+
if (!reasonLabel) {
|
|
95
|
+
const reasons = []
|
|
96
|
+
for (const [key, value] of Object.entries(flagObj)) {
|
|
97
|
+
if (value & flags) {
|
|
98
|
+
reasons.push(key)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const reasonStr = reasons.join(',')
|
|
102
|
+
reasonLabel = labelFromStr(reasonLabelKey, reasonStr)
|
|
103
|
+
reasonLabels[flags] = reasonLabel
|
|
104
|
+
}
|
|
105
|
+
return reasonLabel
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let durationFrom = Number.POSITIVE_INFINITY
|
|
109
|
+
let durationTo = 0
|
|
110
|
+
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
|
|
111
|
+
|
|
112
|
+
const samples = this.entries.map((item) => {
|
|
113
|
+
const { startTime, duration } = item
|
|
114
|
+
const { kind, flags } = node16 ? item.detail : item
|
|
115
|
+
const endTime = startTime + duration
|
|
116
|
+
if (durationFrom > startTime) durationFrom = startTime
|
|
117
|
+
if (durationTo < endTime) durationTo = endTime
|
|
118
|
+
const labels = [
|
|
119
|
+
gcEventLabel,
|
|
120
|
+
threadLabel,
|
|
121
|
+
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) }),
|
|
122
|
+
kindLabels[kind]
|
|
123
|
+
]
|
|
124
|
+
const reasonLabel = getReasonLabel(flags)
|
|
125
|
+
if (reasonLabel) {
|
|
126
|
+
labels.push(reasonLabel)
|
|
127
|
+
}
|
|
128
|
+
const sample = new Sample({
|
|
129
|
+
value: [Math.round(duration * MS_TO_NS)],
|
|
130
|
+
label: labels,
|
|
131
|
+
locationId: locationsPerKind[kind]
|
|
132
|
+
})
|
|
133
|
+
return sample
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
this.entries = []
|
|
137
|
+
|
|
138
|
+
const timeValueType = new ValueType({
|
|
139
|
+
type: stringTable.dedup(pprofValueType),
|
|
140
|
+
unit: stringTable.dedup(pprofValueUnit)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return new Profile({
|
|
144
|
+
sampleType: [timeValueType],
|
|
145
|
+
timeNanos: dateOffset + BigInt(Math.round(durationFrom * MS_TO_NS)),
|
|
146
|
+
periodType: timeValueType,
|
|
147
|
+
period: this._flushIntervalNanos,
|
|
148
|
+
durationNanos: Math.max(0, Math.round((durationTo - durationFrom) * MS_TO_NS)),
|
|
149
|
+
sample: samples,
|
|
150
|
+
location: locations,
|
|
151
|
+
function: functions,
|
|
152
|
+
stringTable: stringTable
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
encode (profile) {
|
|
157
|
+
return pprof.encode(profile)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = EventsProfiler
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../../datadog-core')
|
|
4
4
|
|
|
5
|
-
const dc = require('
|
|
5
|
+
const dc = require('dc-polyfill')
|
|
6
6
|
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
7
|
const { WEB } = require('../../../../../ext/types')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
9
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
10
|
+
const { END_TIMESTAMP, THREAD_NAME, threadNamePrefix } = require('./shared')
|
|
10
11
|
|
|
11
12
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
12
13
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
14
|
+
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
13
15
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
16
|
+
const threadName = `${threadNamePrefix} Event Loop`
|
|
14
17
|
|
|
15
|
-
const
|
|
16
|
-
const { isMainThread, threadId } = require('node:worker_threads')
|
|
17
|
-
const name = isMainThread ? 'Main' : `Worker #${threadId}`
|
|
18
|
-
return `${name} Event Loop`
|
|
19
|
-
})()
|
|
18
|
+
const CachedWebTags = Symbol('NativeWallProfiler.CachedWebTags')
|
|
20
19
|
|
|
21
20
|
let kSampleCount
|
|
22
21
|
|
|
@@ -30,7 +29,11 @@ function getStartedSpans (context) {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
|
|
33
|
-
const labels = {
|
|
32
|
+
const labels = {
|
|
33
|
+
[THREAD_NAME]: threadName,
|
|
34
|
+
// Incoming timestamps are in microseconds, we emit nanos.
|
|
35
|
+
[END_TIMESTAMP]: timestamp * 1000n
|
|
36
|
+
}
|
|
34
37
|
if (spanId) {
|
|
35
38
|
labels['span id'] = spanId
|
|
36
39
|
}
|
|
@@ -43,16 +46,10 @@ function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, t
|
|
|
43
46
|
// fallback to endpoint computed when sample was taken
|
|
44
47
|
labels['trace endpoint'] = endpoint
|
|
45
48
|
}
|
|
46
|
-
// Incoming timestamps are in microseconds, we emit nanos.
|
|
47
|
-
labels['end_timestamp_ns'] = timestamp * 1000n
|
|
48
49
|
|
|
49
50
|
return labels
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
function getSpanContextTags (span) {
|
|
53
|
-
return span.context()._tags
|
|
54
|
-
}
|
|
55
|
-
|
|
56
53
|
function isWebServerSpan (tags) {
|
|
57
54
|
return tags[SPAN_TYPE] === WEB
|
|
58
55
|
}
|
|
@@ -78,6 +75,7 @@ class NativeWallProfiler {
|
|
|
78
75
|
|
|
79
76
|
// Bind to this so the same value can be used to unsubscribe later
|
|
80
77
|
this._enter = this._enter.bind(this)
|
|
78
|
+
this._spanFinished = this._spanFinished.bind(this)
|
|
81
79
|
this._logger = options.logger
|
|
82
80
|
this._started = false
|
|
83
81
|
}
|
|
@@ -120,10 +118,12 @@ class NativeWallProfiler {
|
|
|
120
118
|
this._pprof.time.setContext(this._currentContext)
|
|
121
119
|
this._lastSpan = undefined
|
|
122
120
|
this._lastStartedSpans = undefined
|
|
121
|
+
this._lastWebTags = undefined
|
|
123
122
|
this._lastSampleCount = 0
|
|
124
123
|
|
|
125
124
|
beforeCh.subscribe(this._enter)
|
|
126
125
|
enterCh.subscribe(this._enter)
|
|
126
|
+
spanFinishCh.subscribe(this._spanFinished)
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
this._started = true
|
|
@@ -144,11 +144,43 @@ class NativeWallProfiler {
|
|
|
144
144
|
|
|
145
145
|
const span = getActiveSpan()
|
|
146
146
|
if (span) {
|
|
147
|
+
const context = span.context()
|
|
147
148
|
this._lastSpan = span
|
|
148
|
-
|
|
149
|
+
const startedSpans = getStartedSpans(context)
|
|
150
|
+
this._lastStartedSpans = startedSpans
|
|
151
|
+
if (this._endpointCollectionEnabled) {
|
|
152
|
+
const cachedWebTags = span[CachedWebTags]
|
|
153
|
+
if (cachedWebTags === undefined) {
|
|
154
|
+
let found = false
|
|
155
|
+
// Find the first webspan starting from the end:
|
|
156
|
+
// There might be several webspans, for example with next.js, http plugin creates a first span
|
|
157
|
+
// and then next.js plugin creates a child span, and this child span has the correct endpoint information.
|
|
158
|
+
let nextSpanId = context._spanId
|
|
159
|
+
for (let i = startedSpans.length - 1; i >= 0; i--) {
|
|
160
|
+
const nextContext = startedSpans[i].context()
|
|
161
|
+
if (nextContext._spanId === nextSpanId) {
|
|
162
|
+
const tags = nextContext._tags
|
|
163
|
+
if (isWebServerSpan(tags)) {
|
|
164
|
+
this._lastWebTags = tags
|
|
165
|
+
span[CachedWebTags] = tags
|
|
166
|
+
found = true
|
|
167
|
+
break
|
|
168
|
+
}
|
|
169
|
+
nextSpanId = nextContext._parentId
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!found) {
|
|
173
|
+
this._lastWebTags = undefined
|
|
174
|
+
span[CachedWebTags] = null // cache negative lookup result
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
this._lastWebTags = cachedWebTags
|
|
178
|
+
}
|
|
179
|
+
}
|
|
149
180
|
} else {
|
|
150
181
|
this._lastStartedSpans = undefined
|
|
151
182
|
this._lastSpan = undefined
|
|
183
|
+
this._lastWebTags = undefined
|
|
152
184
|
}
|
|
153
185
|
}
|
|
154
186
|
|
|
@@ -163,21 +195,17 @@ class NativeWallProfiler {
|
|
|
163
195
|
context.rootSpanId = rootSpan.context().toSpanId()
|
|
164
196
|
}
|
|
165
197
|
}
|
|
166
|
-
if (this.
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
context.endpoint = endpointNameFromTags(tags)
|
|
178
|
-
break
|
|
179
|
-
}
|
|
180
|
-
}
|
|
198
|
+
if (this._lastWebTags) {
|
|
199
|
+
context.webTags = this._lastWebTags
|
|
200
|
+
// endpoint may not be determined yet, but keep it as fallback
|
|
201
|
+
// if tags are not available anymore during serialization
|
|
202
|
+
context.endpoint = endpointNameFromTags(this._lastWebTags)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_spanFinished (span) {
|
|
207
|
+
if (span[CachedWebTags]) {
|
|
208
|
+
span[CachedWebTags] = undefined
|
|
181
209
|
}
|
|
182
210
|
}
|
|
183
211
|
|
|
@@ -223,7 +251,11 @@ class NativeWallProfiler {
|
|
|
223
251
|
if (this._withContexts) {
|
|
224
252
|
beforeCh.unsubscribe(this._enter)
|
|
225
253
|
enterCh.unsubscribe(this._enter)
|
|
254
|
+
spanFinishCh.unsubscribe(this._spanFinished)
|
|
226
255
|
this._profilerState = undefined
|
|
256
|
+
this._lastSpan = undefined
|
|
257
|
+
this._lastStartedSpans = undefined
|
|
258
|
+
this._lastWebTags = undefined
|
|
227
259
|
}
|
|
228
260
|
|
|
229
261
|
this._started = false
|
|
@@ -4,7 +4,7 @@ const path = require('path')
|
|
|
4
4
|
const parse = require('module-details-from-path')
|
|
5
5
|
const requirePackageJson = require('../require-package-json')
|
|
6
6
|
const { sendData } = require('./send-data')
|
|
7
|
-
const dc = require('
|
|
7
|
+
const dc = require('dc-polyfill')
|
|
8
8
|
const { fileURLToPath } = require('url')
|
|
9
9
|
|
|
10
10
|
const savedDependenciesToSend = new Set()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const tracerVersion = require('../../../../package.json').version
|
|
4
|
-
const dc = require('
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
5
5
|
const os = require('os')
|
|
6
6
|
const dependencies = require('./dependencies')
|
|
7
7
|
const { sendData } = require('./send-data')
|
|
@@ -31,8 +31,10 @@ class DatadogTracer extends Tracer {
|
|
|
31
31
|
|
|
32
32
|
// todo[piochelepiotr] These two methods are not related to the tracer, but to data streams monitoring.
|
|
33
33
|
// They should be moved outside of the tracer in the future.
|
|
34
|
-
setCheckpoint (edgeTags) {
|
|
35
|
-
const ctx = this._dataStreamsProcessor.setCheckpoint(
|
|
34
|
+
setCheckpoint (edgeTags, span, payloadSize = 0) {
|
|
35
|
+
const ctx = this._dataStreamsProcessor.setCheckpoint(
|
|
36
|
+
edgeTags, span, DataStreamsContext.getDataStreamsContext(), payloadSize
|
|
37
|
+
)
|
|
36
38
|
DataStreamsContext.setDataStreamsContext(ctx)
|
|
37
39
|
return ctx
|
|
38
40
|
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
Channel,
|
|
5
|
-
channel
|
|
6
|
-
} = require('diagnostics_channel') // eslint-disable-line n/no-restricted-require
|
|
7
|
-
|
|
8
|
-
const [major, minor] = process.versions.node.split('.')
|
|
9
|
-
const channels = new WeakSet()
|
|
10
|
-
|
|
11
|
-
// Our own DC with a limited subset of functionality stable across Node versions.
|
|
12
|
-
// TODO: Move the rest of the polyfill here.
|
|
13
|
-
// TODO: Switch to using global subscribe/unsubscribe/hasSubscribers.
|
|
14
|
-
const dc = { channel }
|
|
15
|
-
|
|
16
|
-
// Prevent going to 0 subscribers to avoid bug in Node.
|
|
17
|
-
// See https://github.com/nodejs/node/pull/47520
|
|
18
|
-
if (major === '19' && minor === '9') {
|
|
19
|
-
dc.channel = function () {
|
|
20
|
-
const ch = channel.apply(this, arguments)
|
|
21
|
-
|
|
22
|
-
if (!channels.has(ch)) {
|
|
23
|
-
const subscribe = ch.subscribe
|
|
24
|
-
const unsubscribe = ch.unsubscribe
|
|
25
|
-
|
|
26
|
-
ch.subscribe = function () {
|
|
27
|
-
delete ch.subscribe
|
|
28
|
-
delete ch.unsubscribe
|
|
29
|
-
|
|
30
|
-
const result = subscribe.apply(this, arguments)
|
|
31
|
-
|
|
32
|
-
this.subscribe(() => {}) // Keep it active forever.
|
|
33
|
-
|
|
34
|
-
return result
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (ch.unsubscribe === Channel.prototype.unsubscribe) {
|
|
38
|
-
// Needed because another subscriber could have subscribed to something
|
|
39
|
-
// that we unsubscribe to before the library is loaded.
|
|
40
|
-
ch.unsubscribe = function () {
|
|
41
|
-
delete ch.subscribe
|
|
42
|
-
delete ch.unsubscribe
|
|
43
|
-
|
|
44
|
-
this.subscribe(() => {}) // Keep it active forever.
|
|
45
|
-
|
|
46
|
-
return unsubscribe.apply(this, arguments)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
channels.add(ch)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return ch
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!Channel.prototype.runStores) {
|
|
58
|
-
const ActiveChannelPrototype = getActiveChannelPrototype()
|
|
59
|
-
|
|
60
|
-
Channel.prototype.bindStore = ActiveChannelPrototype.bindStore = function (store, transform) {
|
|
61
|
-
if (!this._stores) {
|
|
62
|
-
this._stores = new Map()
|
|
63
|
-
}
|
|
64
|
-
this._stores.set(store, transform)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
Channel.prototype.unbindStore = ActiveChannelPrototype.unbindStore = function (store) {
|
|
68
|
-
if (!this._stores) return
|
|
69
|
-
this._stores.delete(store)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
Channel.prototype.runStores = ActiveChannelPrototype.runStores = function (data, fn, thisArg, ...args) {
|
|
73
|
-
if (!this._stores) return Reflect.apply(fn, thisArg, args)
|
|
74
|
-
|
|
75
|
-
let run = () => {
|
|
76
|
-
this.publish(data)
|
|
77
|
-
return Reflect.apply(fn, thisArg, args)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const entry of this._stores.entries()) {
|
|
81
|
-
const store = entry[0]
|
|
82
|
-
const transform = entry[1]
|
|
83
|
-
run = wrapStoreRun(store, data, run, transform)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return run()
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function defaultTransform (data) {
|
|
91
|
-
return data
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function wrapStoreRun (store, data, next, transform = defaultTransform) {
|
|
95
|
-
return () => {
|
|
96
|
-
let context
|
|
97
|
-
try {
|
|
98
|
-
context = transform(data)
|
|
99
|
-
} catch (err) {
|
|
100
|
-
process.nextTick(() => {
|
|
101
|
-
throw err
|
|
102
|
-
})
|
|
103
|
-
return next()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return store.run(context, next)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function getActiveChannelPrototype () {
|
|
111
|
-
const dummyChannel = channel('foo')
|
|
112
|
-
const listener = () => {}
|
|
113
|
-
|
|
114
|
-
dummyChannel.subscribe(listener)
|
|
115
|
-
const ActiveChannelPrototype = Object.getPrototypeOf(dummyChannel)
|
|
116
|
-
dummyChannel.unsubscribe(listener)
|
|
117
|
-
|
|
118
|
-
return ActiveChannelPrototype
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = dc
|