@servicenow/sdk-build-core 4.2.0 → 4.3.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/dist/compiler.d.ts +5 -1
- package/dist/compiler.js +104 -7
- package/dist/compiler.js.map +1 -1
- package/dist/now-config-dependencies.d.ts +2 -1
- package/dist/now-config.d.ts +5 -0
- package/dist/now-config.js +2 -4
- package/dist/now-config.js.map +1 -1
- package/dist/plugins/cache.d.ts +8 -13
- package/dist/plugins/cache.js +2 -26
- package/dist/plugins/cache.js.map +1 -1
- package/dist/plugins/data-shape.d.ts +24 -33
- package/dist/plugins/data-shape.js +83 -73
- package/dist/plugins/data-shape.js.map +1 -1
- package/dist/plugins/plugin.d.ts +43 -1
- package/dist/plugins/plugin.js +49 -5
- package/dist/plugins/plugin.js.map +1 -1
- package/dist/plugins/shape.d.ts +19 -11
- package/dist/plugins/shape.js +61 -23
- package/dist/plugins/shape.js.map +1 -1
- package/dist/taxonomy.js +53 -1
- package/dist/taxonomy.js.map +1 -1
- package/dist/telemetry/clients/abstract-client.d.ts +25 -0
- package/dist/telemetry/clients/abstract-client.js +55 -0
- package/dist/telemetry/clients/abstract-client.js.map +1 -0
- package/dist/telemetry/clients/browser-client.d.ts +20 -0
- package/dist/telemetry/clients/browser-client.js +136 -0
- package/dist/telemetry/clients/browser-client.js.map +1 -0
- package/dist/telemetry/clients/node-client.d.ts +15 -0
- package/dist/telemetry/clients/node-client.js +159 -0
- package/dist/telemetry/clients/node-client.js.map +1 -0
- package/dist/telemetry/clients/noop-client.d.ts +10 -0
- package/dist/telemetry/clients/noop-client.js +18 -0
- package/dist/telemetry/clients/noop-client.js.map +1 -0
- package/dist/telemetry/clients/util.d.ts +11 -0
- package/dist/telemetry/clients/util.js +34 -0
- package/dist/telemetry/clients/util.js.map +1 -0
- package/dist/telemetry/config.d.ts +2 -0
- package/dist/telemetry/config.js +28 -0
- package/dist/telemetry/config.js.map +1 -0
- package/dist/telemetry/factory.d.ts +13 -0
- package/dist/telemetry/factory.js +29 -0
- package/dist/telemetry/factory.js.map +1 -0
- package/dist/telemetry/index.d.ts +2 -25
- package/dist/telemetry/index.js +3 -15
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/types.d.ts +55 -0
- package/dist/telemetry/types.js +12 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/typescript.d.ts +10 -0
- package/dist/typescript.js +18 -0
- package/dist/typescript.js.map +1 -1
- package/dist/util/delete-multiple.d.ts +5 -0
- package/dist/util/delete-multiple.js +30 -0
- package/dist/util/delete-multiple.js.map +1 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/index.js.map +1 -1
- package/now.config.schema.json +38 -0
- package/package.json +13 -9
- package/src/compiler.ts +121 -7
- package/src/now-config-dependencies.ts +2 -1
- package/src/now-config.ts +3 -4
- package/src/plugins/cache.ts +5 -27
- package/src/plugins/data-shape.ts +95 -84
- package/src/plugins/plugin.ts +116 -9
- package/src/plugins/shape.ts +64 -30
- package/src/taxonomy.ts +53 -1
- package/src/telemetry/clients/abstract-client.ts +63 -0
- package/src/telemetry/clients/browser-client.ts +160 -0
- package/src/telemetry/clients/node-client.ts +151 -0
- package/src/telemetry/clients/noop-client.ts +15 -0
- package/src/telemetry/clients/util.ts +33 -0
- package/src/telemetry/config.ts +12 -0
- package/src/telemetry/factory.ts +34 -0
- package/src/telemetry/index.ts +2 -27
- package/src/telemetry/types.ts +61 -0
- package/src/typescript.ts +17 -0
- package/src/util/delete-multiple.ts +35 -0
- package/src/util/index.ts +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CallExpressionShape, ObjectShape, Shape, StringShape } from './shape'
|
|
2
2
|
import type { Source } from './product'
|
|
3
3
|
import {
|
|
4
4
|
durationFieldToXML,
|
|
@@ -20,10 +20,11 @@ export const DATA_HELPER_NAMES = {
|
|
|
20
20
|
} as const
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Base class for
|
|
23
|
+
* Base class for data helper shapes that extend CallExpressionShape.
|
|
24
|
+
* These shapes represent call expressions like Duration(), Time(), etc.
|
|
24
25
|
* Provides common equals implementation that compares via toString().
|
|
25
26
|
*/
|
|
26
|
-
abstract class
|
|
27
|
+
abstract class DataHelperShape extends CallExpressionShape {
|
|
27
28
|
abstract override toString(): StringShape
|
|
28
29
|
|
|
29
30
|
override equals(other: unknown): boolean {
|
|
@@ -38,50 +39,30 @@ abstract class SerializableObjectShape extends ObjectShape {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* Shape representing a duration value.
|
|
43
|
+
* Used with Duration() helper - represents Duration({ days, hours, minutes, seconds }) call expression.
|
|
43
44
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
export class DurationShape extends DataHelperShape {
|
|
46
|
+
constructor({ source, value }: { source: Source; value: Duration | ObjectShape }) {
|
|
47
|
+
let durationValue: Duration
|
|
48
|
+
if (value instanceof ObjectShape) {
|
|
49
|
+
const picked = value.pick(['days', 'hours', 'minutes', 'seconds'])
|
|
50
|
+
durationValue = Object.fromEntries(
|
|
51
|
+
Object.entries(picked).map(([k, v]) => [
|
|
52
|
+
k,
|
|
53
|
+
Shape.from(source, v).asNumber(
|
|
54
|
+
`fields in Duration object can only contain numbers, got ${typeof v}`
|
|
55
|
+
),
|
|
56
|
+
])
|
|
57
|
+
) as Duration
|
|
52
58
|
} else {
|
|
53
|
-
|
|
59
|
+
durationValue = value
|
|
54
60
|
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Shape representing a duration value.
|
|
60
|
-
* Used with Duration() helper - stores object with days, hours, minutes, seconds.
|
|
61
|
-
*/
|
|
62
|
-
export class DurationShape extends SerializableObjectShape {
|
|
63
|
-
constructor({ source, value }: { source: Source; value: Duration }) {
|
|
64
|
-
super({ source, properties: value })
|
|
61
|
+
super({ source, callee: DATA_HELPER_NAMES.DURATION, args: [durationValue] })
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
getDuration(): Duration {
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
getDays(): number {
|
|
72
|
-
return this.getDuration().days ?? 0
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getHours(): number {
|
|
76
|
-
return this.getDuration().hours ?? 0
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getMinutes(): number {
|
|
80
|
-
return this.getDuration().minutes ?? 0
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
getSeconds(): number {
|
|
84
|
-
return this.getDuration().seconds ?? 0
|
|
65
|
+
return this.getArgument(0).asObject().getValue() as Duration
|
|
85
66
|
}
|
|
86
67
|
|
|
87
68
|
/**
|
|
@@ -94,51 +75,77 @@ export class DurationShape extends SerializableObjectShape {
|
|
|
94
75
|
return Shape.from(this.getSource(), formatted).asString()
|
|
95
76
|
}
|
|
96
77
|
|
|
97
|
-
static override from(source: Source,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
78
|
+
static override from(source: Source, value: StringShape | ObjectShape): DurationShape {
|
|
79
|
+
if (value instanceof StringShape) {
|
|
80
|
+
const parsedValue = parseGlideDuration(value.getValue())
|
|
81
|
+
if (!parsedValue) {
|
|
82
|
+
throw new Error(`Invalid duration value: ${value.getValue()}`)
|
|
83
|
+
}
|
|
84
|
+
return new DurationShape({
|
|
85
|
+
source,
|
|
86
|
+
value: parsedValue,
|
|
87
|
+
})
|
|
101
88
|
}
|
|
102
89
|
|
|
103
90
|
return new DurationShape({
|
|
104
91
|
source,
|
|
105
|
-
value:
|
|
92
|
+
value: value,
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a DurationShape representing zero duration.
|
|
98
|
+
* Use this when the platform returns an empty or default duration value.
|
|
99
|
+
* @example DurationShape.zero(source) // Returns Duration({ seconds: 0 })
|
|
100
|
+
*/
|
|
101
|
+
static zero(source: Source): DurationShape {
|
|
102
|
+
return new DurationShape({
|
|
103
|
+
source,
|
|
104
|
+
value: { seconds: 0 },
|
|
106
105
|
})
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
110
|
* Shape representing a time of day value.
|
|
112
|
-
* Used with Time() helper -
|
|
111
|
+
* Used with Time() helper - represents Time({ hours, minutes, seconds }, timezone?) call expression.
|
|
113
112
|
*/
|
|
114
|
-
export class TimeShape extends
|
|
113
|
+
export class TimeShape extends DataHelperShape {
|
|
115
114
|
private readonly timeZone: string | undefined
|
|
116
115
|
|
|
117
|
-
constructor({
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
constructor({
|
|
117
|
+
source,
|
|
118
|
+
value,
|
|
119
|
+
timeZone,
|
|
120
|
+
}: { source: Source; value: TimeOfDay | ObjectShape; timeZone?: string | undefined }) {
|
|
121
|
+
let timeValue: TimeOfDay
|
|
122
|
+
if (value instanceof ObjectShape) {
|
|
123
|
+
const picked = value.pick(['hours', 'minutes', 'seconds'])
|
|
124
|
+
timeValue = Object.fromEntries(
|
|
125
|
+
Object.entries(picked).map(([k, v]) => [
|
|
126
|
+
k,
|
|
127
|
+
Shape.from(source, v).asNumber(`fields in Time object can only contain numbers, got ${typeof v}`),
|
|
128
|
+
])
|
|
129
|
+
) as TimeOfDay
|
|
130
|
+
} else {
|
|
131
|
+
timeValue = value
|
|
132
|
+
}
|
|
133
|
+
const args: unknown[] = [timeValue]
|
|
134
|
+
if (timeZone) {
|
|
135
|
+
args.push(timeZone)
|
|
136
|
+
}
|
|
137
|
+
super({ source, callee: DATA_HELPER_NAMES.TIME, args })
|
|
138
|
+
this.timeZone = timeZone
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
getTimeData(): TimeOfDay {
|
|
123
|
-
return
|
|
142
|
+
return this.getArgument(0).asObject().getValue() as TimeOfDay
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
getTimeZone(): string | undefined {
|
|
127
146
|
return this.timeZone
|
|
128
147
|
}
|
|
129
148
|
|
|
130
|
-
getHours(): number {
|
|
131
|
-
return this.getTimeData().hours ?? 0
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
getMinutes(): number {
|
|
135
|
-
return this.getTimeData().minutes ?? 0
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
getSeconds(): number {
|
|
139
|
-
return this.getTimeData().seconds ?? 0
|
|
140
|
-
}
|
|
141
|
-
|
|
142
149
|
/**
|
|
143
150
|
* Returns a StringShape with the ServiceNow serialized format.
|
|
144
151
|
* @example '1970-01-01 14:30:00' for { hours: 14, minutes: 30 }
|
|
@@ -149,14 +156,22 @@ export class TimeShape extends SerializableObjectShape {
|
|
|
149
156
|
return Shape.from(this.getSource(), formatted).toString()
|
|
150
157
|
}
|
|
151
158
|
|
|
152
|
-
static override from(source: Source,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
static override from(source: Source, value: StringShape | ObjectShape, timeZone?: string): TimeShape {
|
|
160
|
+
if (value instanceof StringShape) {
|
|
161
|
+
const parsedValue = parseGlideTime(value.getValue(), timeZone)
|
|
162
|
+
if (!parsedValue) {
|
|
163
|
+
throw new Error(`Invalid time value: ${value.getValue()}`)
|
|
164
|
+
}
|
|
165
|
+
return new TimeShape({
|
|
166
|
+
source,
|
|
167
|
+
value: parsedValue,
|
|
168
|
+
timeZone,
|
|
169
|
+
})
|
|
156
170
|
}
|
|
171
|
+
|
|
157
172
|
return new TimeShape({
|
|
158
173
|
source,
|
|
159
|
-
value:
|
|
174
|
+
value: value,
|
|
160
175
|
timeZone,
|
|
161
176
|
})
|
|
162
177
|
}
|
|
@@ -164,15 +179,15 @@ export class TimeShape extends SerializableObjectShape {
|
|
|
164
179
|
|
|
165
180
|
/**
|
|
166
181
|
* Shape representing a field list.
|
|
167
|
-
* Used with FieldList() helper -
|
|
182
|
+
* Used with FieldList() helper - represents FieldList(["field1", "field2"]) call expression.
|
|
168
183
|
*/
|
|
169
|
-
export class FieldListShape extends
|
|
184
|
+
export class FieldListShape extends DataHelperShape {
|
|
170
185
|
constructor({ source, fields }: { source: Source; fields: Shape[] }) {
|
|
171
|
-
super({ source,
|
|
186
|
+
super({ source, callee: DATA_HELPER_NAMES.FIELD_LIST, args: [fields] })
|
|
172
187
|
}
|
|
173
188
|
|
|
174
189
|
getFields(): Shape[] {
|
|
175
|
-
return
|
|
190
|
+
return this.getArgument(0).asArray().getElements()
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
/**
|
|
@@ -198,19 +213,15 @@ export class FieldListShape extends SerializableArrayShape {
|
|
|
198
213
|
|
|
199
214
|
/**
|
|
200
215
|
* Shape representing a template value (ServiceNow encoded query format).
|
|
201
|
-
* Used with TemplateValue() helper -
|
|
216
|
+
* Used with TemplateValue() helper - represents TemplateValue({ field: value }) call expression.
|
|
202
217
|
*/
|
|
203
|
-
export class TemplateValueShape extends
|
|
218
|
+
export class TemplateValueShape extends DataHelperShape {
|
|
204
219
|
constructor({ source, value }: { source: Source; value: globalThis.Record<string, unknown> }) {
|
|
205
|
-
super({ source,
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
getFields(): string[] {
|
|
209
|
-
return Object.keys(this.getValue())
|
|
220
|
+
super({ source, callee: DATA_HELPER_NAMES.TEMPLATE_VALUE, args: [value] })
|
|
210
221
|
}
|
|
211
222
|
|
|
212
|
-
|
|
213
|
-
return this.
|
|
223
|
+
getTemplateValue(): ObjectShape {
|
|
224
|
+
return this.getArgument(0).asObject()
|
|
214
225
|
}
|
|
215
226
|
|
|
216
227
|
/**
|
|
@@ -220,8 +231,8 @@ export class TemplateValueShape extends SerializableObjectShape {
|
|
|
220
231
|
* @example 'name=value^^with^^caret^EQ' for { name: 'value^with^caret' }
|
|
221
232
|
*/
|
|
222
233
|
override toString(): StringShape {
|
|
223
|
-
const
|
|
224
|
-
const encoded = entries
|
|
234
|
+
const templateValue = this.getTemplateValue().getValue()
|
|
235
|
+
const encoded = Object.entries(templateValue)
|
|
225
236
|
.map(([key, value]) => {
|
|
226
237
|
// Escape single ^ as ^^ in values
|
|
227
238
|
const escapedValue = String(value).replace(/\^/g, '^^')
|
package/src/plugins/plugin.ts
CHANGED
|
@@ -25,6 +25,11 @@ export type Result<Value = unknown> =
|
|
|
25
25
|
success: true
|
|
26
26
|
value: Value
|
|
27
27
|
}
|
|
28
|
+
| {
|
|
29
|
+
success: 'partial'
|
|
30
|
+
value: Value
|
|
31
|
+
unhandledRecords: Record[]
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
export type CommitResult = { success: boolean }
|
|
30
35
|
|
|
@@ -92,6 +97,36 @@ export type CoalesceStrategy =
|
|
|
92
97
|
|
|
93
98
|
export type FileType = 'fluent' | 'module' | 'json' | 'unknown'
|
|
94
99
|
|
|
100
|
+
export type PluginApiDoc = {
|
|
101
|
+
/**
|
|
102
|
+
* The name of the API (e.g., 'BusinessRule', 'Acl', 'Table')
|
|
103
|
+
* This is the callee name used in fluent code.
|
|
104
|
+
*/
|
|
105
|
+
apiName: string
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Uses module-specifier path to the markdown documentation file for this API relative to the SDK root.
|
|
109
|
+
* For example, @servicenow/sdk/docs/BusinessRule/BusinessRule.md
|
|
110
|
+
*/
|
|
111
|
+
docPath: string
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Tags for categorizing and organizing the API documentation
|
|
115
|
+
*/
|
|
116
|
+
tags: string[]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Manifest structure for API documentation.
|
|
121
|
+
* This is the opinionated format returned by getDocsMetadata().
|
|
122
|
+
*/
|
|
123
|
+
export type DocsManifest = {
|
|
124
|
+
[apiName: string]: {
|
|
125
|
+
docPath: string
|
|
126
|
+
tags: string[]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
95
130
|
export type PluginConfig<
|
|
96
131
|
Nodes extends SupportedKindName[] = SupportedKindName[],
|
|
97
132
|
Shapes extends ShapeClass[] = ShapeClass[],
|
|
@@ -103,6 +138,12 @@ export type PluginConfig<
|
|
|
103
138
|
*/
|
|
104
139
|
name: `${string}Plugin`
|
|
105
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Documentation for the APIs provided by this plugin. This is used to automatically
|
|
143
|
+
* generate documentation that can be retrieved via the getDocsMetadata() method.
|
|
144
|
+
*/
|
|
145
|
+
docs?: PluginApiDoc[]
|
|
146
|
+
|
|
106
147
|
/**
|
|
107
148
|
* The TypeScript AST nodes this plugin handles. Plugins that do not introduce new
|
|
108
149
|
* syntax should not need to define any handlers here.
|
|
@@ -380,7 +421,12 @@ export type PluginConfig<
|
|
|
380
421
|
* @returns A result indicating whether diffing was successful and, if so, a database
|
|
381
422
|
* containing only the changed records
|
|
382
423
|
*/
|
|
383
|
-
diff?: (
|
|
424
|
+
diff?: (
|
|
425
|
+
existing: Database,
|
|
426
|
+
incoming: Database,
|
|
427
|
+
descendants: Database,
|
|
428
|
+
context: Context
|
|
429
|
+
) => Promise<Result<Database>>
|
|
384
430
|
}
|
|
385
431
|
}
|
|
386
432
|
|
|
@@ -595,6 +641,28 @@ export class Plugin {
|
|
|
595
641
|
return this.config.name
|
|
596
642
|
}
|
|
597
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Get documentation metadata for APIs provided by this plugin.
|
|
646
|
+
* Returns a manifest object with raw (unresolved) documentation paths.
|
|
647
|
+
* Path resolution is handled by PluginRegistry.getDocsMetadata() using the isomorphic resolver.
|
|
648
|
+
*/
|
|
649
|
+
getDocsMetadata(): DocsManifest | undefined {
|
|
650
|
+
if (!this.config.docs) {
|
|
651
|
+
return undefined
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const manifest: DocsManifest = {}
|
|
655
|
+
|
|
656
|
+
for (const doc of this.config.docs) {
|
|
657
|
+
manifest[doc.apiName] = {
|
|
658
|
+
docPath: doc.docPath,
|
|
659
|
+
tags: doc.tags,
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return manifest
|
|
664
|
+
}
|
|
665
|
+
|
|
598
666
|
getDescendants(parent: Record, database: Database): Record[] {
|
|
599
667
|
const descendantRelationships = Object.entries(this.relationships[parent.getTable()] ?? {}).filter(
|
|
600
668
|
([, { descendant }]) => descendant
|
|
@@ -720,7 +788,7 @@ export class Plugin {
|
|
|
720
788
|
async nodeToShape(node: SupportedNode, context: Omit<Context, 'self'>): Promise<Result<Shape>> {
|
|
721
789
|
const entry = this.nodeToShapeCache.get(node.compilerNode)
|
|
722
790
|
if (entry) {
|
|
723
|
-
return entry.
|
|
791
|
+
return entry.success ? entry.value : { success: false }
|
|
724
792
|
}
|
|
725
793
|
|
|
726
794
|
try {
|
|
@@ -773,7 +841,7 @@ export class Plugin {
|
|
|
773
841
|
async shapeToSubclass<const S extends Shape>(shape: S, context: Omit<Context, 'self'>): Promise<Result<S>> {
|
|
774
842
|
const entry = this.shapeToSubclassCache.get(shape)
|
|
775
843
|
if (entry) {
|
|
776
|
-
return entry.
|
|
844
|
+
return entry.success ? (entry.value as Result<S>) : { success: false }
|
|
777
845
|
}
|
|
778
846
|
|
|
779
847
|
try {
|
|
@@ -813,7 +881,7 @@ export class Plugin {
|
|
|
813
881
|
async shapeToRecord(shape: Shape, context: Omit<Context, 'self'>): Promise<Result<Record>> {
|
|
814
882
|
const entry = this.shapeToRecordCache.get(shape)
|
|
815
883
|
if (entry) {
|
|
816
|
-
return entry.
|
|
884
|
+
return entry.success ? entry.value : { success: false }
|
|
817
885
|
}
|
|
818
886
|
|
|
819
887
|
try {
|
|
@@ -910,6 +978,8 @@ export class Plugin {
|
|
|
910
978
|
continue
|
|
911
979
|
}
|
|
912
980
|
|
|
981
|
+
context.logger.debug(`Database contains ${database.query().length} records for transformation.`)
|
|
982
|
+
|
|
913
983
|
context.logger.debug(
|
|
914
984
|
`Transforming record into shape: ${record.getTable()}.${record.getId().getValue()}`
|
|
915
985
|
)
|
|
@@ -943,12 +1013,34 @@ export class Plugin {
|
|
|
943
1013
|
descendants: nonDeletedDescendants,
|
|
944
1014
|
})
|
|
945
1015
|
|
|
1016
|
+
// Track unhandled records to avoid marking them as handled
|
|
1017
|
+
const unhandledRecords: Record[] = []
|
|
1018
|
+
const unhandledRecordIds: string[] = []
|
|
1019
|
+
|
|
1020
|
+
if (result.success === 'partial') {
|
|
1021
|
+
for (const unhandledRecord of result.unhandledRecords) {
|
|
1022
|
+
unhandledRecords.push(unhandledRecord)
|
|
1023
|
+
unhandledRecordIds.push(unhandledRecord.getId().getValue())
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
946
1027
|
if (result.success) {
|
|
947
1028
|
success = true
|
|
948
1029
|
handledRecords.insert(record)
|
|
949
1030
|
shapes.push(...[result.value].flat())
|
|
1031
|
+
|
|
1032
|
+
// Mark all descendants as handled
|
|
950
1033
|
for (const descendant of [...deletedDescendants, ...nonDeletedDescendants]) {
|
|
951
|
-
|
|
1034
|
+
if (!unhandledRecordIds.includes(descendant.getId().getValue())) {
|
|
1035
|
+
handledRecords.insert(descendant)
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
context.logger.debug(
|
|
1039
|
+
`Plugin ${this.getName()} deferred handling of records: ${unhandledRecords.map((r) => r.getTable() + '_' + r.getId().getValue()).join(', ')}`
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
if (unhandledRecords.length > 0) {
|
|
1043
|
+
return { success: 'partial', value: shapes, unhandledRecords }
|
|
952
1044
|
}
|
|
953
1045
|
}
|
|
954
1046
|
}
|
|
@@ -998,10 +1090,15 @@ export class Plugin {
|
|
|
998
1090
|
...[incoming.resolve(record.getId())].filter((r) => r !== undefined),
|
|
999
1091
|
...incoming.query().filter((r) => descendantsDatabase.resolve(r.getId()) !== undefined),
|
|
1000
1092
|
]
|
|
1001
|
-
const result = await diff.bind(this)(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1093
|
+
const result = await diff.bind(this)(
|
|
1094
|
+
new Database(existingTree),
|
|
1095
|
+
new Database(incomingTree),
|
|
1096
|
+
new Database(descendants),
|
|
1097
|
+
{
|
|
1098
|
+
...context,
|
|
1099
|
+
self: this,
|
|
1100
|
+
}
|
|
1101
|
+
)
|
|
1005
1102
|
|
|
1006
1103
|
if (result.success) {
|
|
1007
1104
|
changedRecords.push(...result.value.query())
|
|
@@ -1245,4 +1342,14 @@ export class Plugins {
|
|
|
1245
1342
|
|
|
1246
1343
|
return false
|
|
1247
1344
|
}
|
|
1345
|
+
|
|
1346
|
+
getDocsMetadata(): DocsManifest {
|
|
1347
|
+
return this.plugins.reduce((aggregated, plugin) => {
|
|
1348
|
+
const pluginManifest = plugin.getDocsMetadata()
|
|
1349
|
+
if (pluginManifest) {
|
|
1350
|
+
Object.assign(aggregated, pluginManifest)
|
|
1351
|
+
}
|
|
1352
|
+
return aggregated
|
|
1353
|
+
}, {} as DocsManifest)
|
|
1354
|
+
}
|
|
1248
1355
|
}
|
package/src/plugins/shape.ts
CHANGED
|
@@ -366,6 +366,10 @@ export class UndefinedShape extends Shape<undefined> {
|
|
|
366
366
|
override toString(): StringShape {
|
|
367
367
|
return Shape.from(this, '').asString()
|
|
368
368
|
}
|
|
369
|
+
|
|
370
|
+
override getCode(): string {
|
|
371
|
+
return 'undefined'
|
|
372
|
+
}
|
|
369
373
|
}
|
|
370
374
|
|
|
371
375
|
export class DeletedShape extends UndefinedShape {}
|
|
@@ -473,6 +477,25 @@ export class PropertyAccessShape extends ResolvableShape {
|
|
|
473
477
|
override getCode(): string {
|
|
474
478
|
return this.elements.map((e) => e.getCode()).join('.')
|
|
475
479
|
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Override equals to compare property access paths instead of resolved values.
|
|
483
|
+
* This is important for datapill references where we need to compare the actual
|
|
484
|
+
* property paths (e.g., params.trigger.table_name vs params.trigger.current.user_name)
|
|
485
|
+
* rather than their resolved values (which would both be UnresolvedShape).
|
|
486
|
+
*/
|
|
487
|
+
override equals(other: unknown): boolean {
|
|
488
|
+
if (other instanceof PropertyAccessShape) {
|
|
489
|
+
// Compare the property access paths element by element
|
|
490
|
+
const otherElements = other.getElements()
|
|
491
|
+
if (this.elements.length !== otherElements.length) {
|
|
492
|
+
return false
|
|
493
|
+
}
|
|
494
|
+
return this.elements.every((el, i) => el.getName() === otherElements[i]?.getName())
|
|
495
|
+
}
|
|
496
|
+
// Fall back to default behavior for non-PropertyAccessShape comparisons
|
|
497
|
+
return super.equals(other)
|
|
498
|
+
}
|
|
476
499
|
}
|
|
477
500
|
|
|
478
501
|
type StringContentType = 'plain' | 'cdata'
|
|
@@ -800,7 +823,7 @@ const coerceTo = (coerceToType: 'string' | 'number' | 'boolean' | 'cdata', value
|
|
|
800
823
|
}
|
|
801
824
|
}
|
|
802
825
|
|
|
803
|
-
class
|
|
826
|
+
export class ShapeTransform {
|
|
804
827
|
constructor(
|
|
805
828
|
private readonly shape: ObjectShape,
|
|
806
829
|
private readonly resolve = true,
|
|
@@ -844,35 +867,35 @@ class Transform {
|
|
|
844
867
|
}
|
|
845
868
|
|
|
846
869
|
from(...properties: [string, ...string[]]) {
|
|
847
|
-
return new
|
|
870
|
+
return new ShapeTransform(this.shape, this.resolve, properties, this._map, this._def, this._coerce)
|
|
848
871
|
}
|
|
849
872
|
|
|
850
873
|
map(mapFunction: (...from: Shape[]) => unknown) {
|
|
851
|
-
return new
|
|
874
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, mapFunction, this._def, this._coerce)
|
|
852
875
|
}
|
|
853
876
|
|
|
854
877
|
val(value: unknown) {
|
|
855
|
-
return new
|
|
878
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, () => value, this._def, this._coerce)
|
|
856
879
|
}
|
|
857
880
|
|
|
858
881
|
def(value: unknown) {
|
|
859
|
-
return new
|
|
882
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, this._map, value, this._coerce)
|
|
860
883
|
}
|
|
861
884
|
|
|
862
885
|
toCdata() {
|
|
863
|
-
return new
|
|
886
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, this._map, this._def, 'cdata')
|
|
864
887
|
}
|
|
865
888
|
|
|
866
889
|
toString() {
|
|
867
|
-
return new
|
|
890
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, this._map, this._def, 'string')
|
|
868
891
|
}
|
|
869
892
|
|
|
870
893
|
toNumber() {
|
|
871
|
-
return new
|
|
894
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, this._map, this._def, 'number')
|
|
872
895
|
}
|
|
873
896
|
|
|
874
897
|
toBoolean() {
|
|
875
|
-
return new
|
|
898
|
+
return new ShapeTransform(this.shape, this.resolve, this._from, this._map, this._def, 'boolean')
|
|
876
899
|
}
|
|
877
900
|
}
|
|
878
901
|
|
|
@@ -936,26 +959,37 @@ export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
|
|
|
936
959
|
nonDefaultsOnly = false,
|
|
937
960
|
resolve = true,
|
|
938
961
|
}: ObjectPropertyOptions = {}): globalThis.Record<string, Shape> {
|
|
962
|
+
const result: globalThis.Record<string, Shape> = {}
|
|
963
|
+
|
|
939
964
|
if (nonDefaultsOnly) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
965
|
+
// Only include properties that differ from defaults
|
|
966
|
+
for (const k in this.setProperties) {
|
|
967
|
+
const v = this.setProperties[k]
|
|
968
|
+
if (v && v.isDefined() && !v.equals(this.getDefault(k))) {
|
|
969
|
+
result[k] = resolve && v.isResolvable() ? v.resolve() : v
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return result
|
|
973
|
+
}
|
|
943
974
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
975
|
+
// Iterate set properties first to preserve ordering
|
|
976
|
+
for (const k in this.setProperties) {
|
|
977
|
+
const v = this.setProperties[k]
|
|
978
|
+
if (v) {
|
|
979
|
+
result[k] = resolve && v.isResolvable() ? v.resolve() : v
|
|
980
|
+
}
|
|
947
981
|
}
|
|
948
982
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
983
|
+
for (const k in this.defaultProperties) {
|
|
984
|
+
if (!this.setProperties[k] || this.setProperties[k].isUndefined()) {
|
|
985
|
+
const v = this.defaultProperties[k]
|
|
986
|
+
if (v) {
|
|
987
|
+
result[k] = resolve && v.isResolvable() ? v.resolve() : v
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
957
991
|
|
|
958
|
-
return
|
|
992
|
+
return result
|
|
959
993
|
}
|
|
960
994
|
|
|
961
995
|
override getValue(nonDefaultsOnly = false): globalThis.Record<string, unknown> {
|
|
@@ -966,8 +1000,8 @@ export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
|
|
|
966
1000
|
const [property, next, ...rest] = typeof propertyOrPath === 'string' ? [propertyOrPath] : propertyOrPath
|
|
967
1001
|
const propertyAliases = [property, ...this.getAliases(property)]
|
|
968
1002
|
const properties = this.properties({ resolve })
|
|
969
|
-
const match = propertyAliases.find((p) => properties
|
|
970
|
-
const value = match ? properties[match] : undefined
|
|
1003
|
+
const match = propertyAliases.find((p) => p in properties)
|
|
1004
|
+
const value = match !== undefined ? properties[match] : undefined
|
|
971
1005
|
return (
|
|
972
1006
|
(next ? value?.ifObject()?.get([next, ...rest], resolve) : value) ??
|
|
973
1007
|
new UndefinedShape({ source: this.getSource() })
|
|
@@ -1035,15 +1069,15 @@ export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
|
|
|
1035
1069
|
}
|
|
1036
1070
|
|
|
1037
1071
|
transform(
|
|
1038
|
-
schema: (utils: { $:
|
|
1039
|
-
[P in string | typeof MERGE]?:
|
|
1072
|
+
schema: (utils: { $: ShapeTransform; merge: typeof MERGE }) => {
|
|
1073
|
+
[P in string | typeof MERGE]?: ShapeTransform
|
|
1040
1074
|
},
|
|
1041
1075
|
resolve = true
|
|
1042
1076
|
) {
|
|
1043
1077
|
const properties = {}
|
|
1044
1078
|
const defaults = {}
|
|
1045
1079
|
|
|
1046
|
-
const definedSchema = schema({ merge: MERGE, $: new
|
|
1080
|
+
const definedSchema = schema({ merge: MERGE, $: new ShapeTransform(this, resolve) })
|
|
1047
1081
|
for (const to of [...Object.keys(definedSchema), MERGE] as const) {
|
|
1048
1082
|
if (definedSchema[to]) {
|
|
1049
1083
|
const result = definedSchema[to]._(to)
|
|
@@ -1099,7 +1133,7 @@ export class ObjectShape extends Shape<globalThis.Record<string, unknown>> {
|
|
|
1099
1133
|
} else if (a[key]?.equals(value)) {
|
|
1100
1134
|
// Don't overwrite properties that are equal
|
|
1101
1135
|
} else if (a[key]?.isObject() && value.isObject()) {
|
|
1102
|
-
a[key].merge(value) // Merge objects recursively
|
|
1136
|
+
a[key] = a[key].merge(value) // Merge objects recursively
|
|
1103
1137
|
} else {
|
|
1104
1138
|
a[key] = value
|
|
1105
1139
|
}
|