@enfocussw/switch-scripting-context 25.11.0-beta.8 → 25.11.1-beta.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/CHANGELOG.md +246 -0
- package/README.md +45 -48
- package/dist/init.d.ts +15 -0
- package/dist/init.js +77 -66
- package/docs/switch-api/api-versions.md +116 -0
- package/docs/switch-api/connection.md +82 -0
- package/docs/switch-api/{api-document-classes.md → document-classes.md} +13 -2
- package/docs/switch-api/entry-points.md +171 -0
- package/docs/switch-api/{api-enums.md → enums.md} +23 -12
- package/docs/switch-api/{api-execution-environment.md → execution-environment.md} +39 -12
- package/docs/switch-api/{api-flow-element.md → flow-element.md} +23 -7
- package/docs/switch-api/{api-http.md → http.md} +10 -1
- package/docs/switch-api/job-patterns.md +178 -0
- package/docs/switch-api/{api-job.md → job.md} +23 -9
- package/docs/switch-api/{api-logging.md → logging.md} +47 -5
- package/docs/switch-api/{api-switch.md → switch.md} +68 -4
- package/docs/switch-appstore/app-guidelines.md +258 -0
- package/docs/switch-appstore/app-manual.md +84 -0
- package/docs/switch-appstore/app-store-listing.md +69 -0
- package/docs/switch-appstore/app-store-submission.md +83 -0
- package/docs/{switch-api/api-debugging.md → switch-project/debugging.md} +9 -0
- package/docs/{switch-api/api-logs-and-dataroot.md → switch-project/logs-and-dataroot.md} +10 -1
- package/docs/switch-project/project-planning.md +117 -0
- package/docs/switch-project/property-documentation.md +75 -0
- package/docs/{switch-api/api-property-editors.md → switch-project/property-editors.md} +55 -8
- package/docs/{switch-api/api-script-declaration.md → switch-project/script-declaration.md} +47 -13
- package/docs/switch-project/script-structure.md +188 -0
- package/docs/{switch-api/api-tooling.md → switch-project/tooling.md} +87 -1
- package/docs/{switch-api/api-vscode.md → switch-project/vscode.md} +19 -0
- package/docs/switch-scripting.md +49 -23
- package/package.json +11 -7
- package/docs/switch-api/api-connection.md +0 -63
- package/docs/switch-api/api-entry-points.md +0 -82
- package/docs/switch-api/api-job-patterns.md +0 -79
- package/docs/switch-api/api-script-structure.md +0 -85
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: flow-element
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 4
|
|
5
|
+
summary: "`FlowElement`: properties, connections, job creation, logging"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Reading properties, creating jobs, logging, connections"
|
|
8
|
+
---
|
|
9
|
+
|
|
1
10
|
# FlowElement Class
|
|
2
11
|
|
|
3
12
|
The `FlowElement` instance `flowElement` is passed to every entry point that operates on a flow element. It provides access to properties, connections, job creation, and logging.
|
|
@@ -5,11 +14,13 @@ The `FlowElement` instance `flowElement` is passed to every entry point that ope
|
|
|
5
14
|
## Identity
|
|
6
15
|
|
|
7
16
|
```ts
|
|
17
|
+
// Switch 21.1+
|
|
8
18
|
flowElement.getName(): string
|
|
9
19
|
```
|
|
10
20
|
Returns the name of this flow element.
|
|
11
21
|
|
|
12
22
|
```ts
|
|
23
|
+
// Switch 22.0+
|
|
13
24
|
flowElement.getFlowName(): string
|
|
14
25
|
```
|
|
15
26
|
Returns the name of the flow containing this element.
|
|
@@ -21,11 +32,11 @@ Properties are configured by the user in the Switch canvas and declared in the s
|
|
|
21
32
|
```ts
|
|
22
33
|
flowElement.getPropertyStringValue(tag: string): Promise<string | string[]>
|
|
23
34
|
```
|
|
24
|
-
Returns the property value as a string (or array for multi-value properties). For `OAuthToken` properties, returns a valid refreshed token. Throws `"Invalid tag: <tag>"` if the tag is unknown or a hidden dependent property. See [
|
|
35
|
+
Returns the property value as a string (or array for multi-value properties). For `OAuthToken` properties, returns a valid refreshed token. Throws `"Invalid tag: <tag>"` if the tag is unknown **or if it's a `Dependency` dependent currently hidden by its master's value** — see [script-declaration.md § Dependency](../switch-project/script-declaration.md#custom-property-elements-elementfields). Before fetching a dependent property, confirm its condition holds (from the already-read master value, or via `hasProperty(tag)` below) rather than fetching it unconditionally. See [property-editors.md](../switch-project/property-editors.md) for editor types and their return value formats.
|
|
25
36
|
|
|
26
37
|
> **Dynamic values (Switch variables and script expressions) are only resolved when `getPropertyStringValue()` is called from `jobArrived`.** In any other entry point (e.g. `timerFired`) the raw unresolved string is returned.
|
|
27
38
|
>
|
|
28
|
-
> **Workaround for deferred processing:** Read property values in `jobArrived` and store them with `s.setGlobalData()`. In `timerFired`, retrieve the stored values and fetch the waiting jobs with `flowElement.getJobs(ids)
|
|
39
|
+
> **Workaround for deferred processing:** Read property values in `jobArrived` and store them with `s.setGlobalData()`. In `timerFired`, retrieve the stored values and fetch the waiting jobs with `flowElement.getJobs(ids)` (Switch 21.0+).
|
|
29
40
|
|
|
30
41
|
```ts
|
|
31
42
|
flowElement.getPropertyType(tag: string): PropertyType
|
|
@@ -40,7 +51,7 @@ Returns the English display name of the property (for use in log messages).
|
|
|
40
51
|
```ts
|
|
41
52
|
flowElement.hasProperty(tag: string): boolean
|
|
42
53
|
```
|
|
43
|
-
Returns `true` if the property exists and is visible for the current configuration.
|
|
54
|
+
Returns `true` if the property exists and is visible for the current configuration. Use this to guard `getPropertyStringValue(tag)` calls on `Dependency` dependents — see the note above.
|
|
44
55
|
|
|
45
56
|
## Connections
|
|
46
57
|
|
|
@@ -58,7 +69,7 @@ Sets the interval between `timerFired` invocations. Default is 300 s. The actual
|
|
|
58
69
|
|
|
59
70
|
## Logging
|
|
60
71
|
|
|
61
|
-
See [
|
|
72
|
+
See [logging.md](logging.md) for log level semantics and logging practice.
|
|
62
73
|
|
|
63
74
|
```ts
|
|
64
75
|
flowElement.log(level: LogLevel, message: string, messageParams?: (string | number | boolean)[]): Promise<void>
|
|
@@ -68,16 +79,17 @@ Logs a message for the flow element. Use `%1`, `%2`, etc. in the message string
|
|
|
68
79
|
```ts
|
|
69
80
|
flowElement.failProcess(message: string, messageParam?: string | number | boolean): void
|
|
70
81
|
```
|
|
71
|
-
Logs a fatal error and puts the element into the "problem process" state. `messageParam` substitutes `%1
|
|
82
|
+
Logs a fatal error and puts the element into the "problem process" state. `messageParam` substitutes `%1`; before Switch 22.1, always pass it, as a string (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)). Throws `"Job routing is not allowed in this entry point."` if called from an entry point where routing isn't permitted.
|
|
72
83
|
|
|
73
84
|
## Job creation (jobArrived / timerFired)
|
|
74
85
|
|
|
75
86
|
```ts
|
|
76
87
|
flowElement.createJob(path: string): Promise<Job>
|
|
77
88
|
```
|
|
78
|
-
Creates a new job from an existing file/folder path. Valid only in `jobArrived` and `timerFired`. The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [
|
|
89
|
+
Creates a new job from an existing file/folder path. Valid only in `jobArrived` and `timerFired`. The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [job-patterns.md](job-patterns.md#temp-file-cleanup) for temp file cleanup rules and [job-patterns.md](job-patterns.md#sending-jobs) for routing constraints.
|
|
79
90
|
|
|
80
91
|
```ts
|
|
92
|
+
// Switch 21.0+
|
|
81
93
|
flowElement.getJobs(ids: string[]): Promise<Job[]>
|
|
82
94
|
```
|
|
83
95
|
Returns up to 10,000 waiting jobs by ID. Throws if `ids` is not an array, is empty, or exceeds 10,000 entries. Call at most once per entry point. Not available in concurrent `jobArrived`. Private data and metadata of returned jobs cannot be read in the same invocation.
|
|
@@ -85,6 +97,7 @@ Returns up to 10,000 waiting jobs by ID. Throws if `ids` is not an array, is emp
|
|
|
85
97
|
## Channels
|
|
86
98
|
|
|
87
99
|
```ts
|
|
100
|
+
// Switch 22.1+
|
|
88
101
|
flowElement.subscribeToChannel(channelId: string, backingFolderPath: string): void
|
|
89
102
|
```
|
|
90
103
|
Subscribes to a channel to receive jobs from it. Only one subscriber per channel at a time. Must be called inside `flowStartTriggered`.
|
|
@@ -92,16 +105,19 @@ Subscribes to a channel to receive jobs from it. Only one subscriber per channel
|
|
|
92
105
|
## Utilities
|
|
93
106
|
|
|
94
107
|
```ts
|
|
108
|
+
// Switch 24.0+
|
|
95
109
|
flowElement.createPathWithName(name: string, createFolder: boolean): Promise<any>
|
|
96
110
|
```
|
|
97
|
-
Creates a temporary path (optionally as a folder — `createFolder` is required, not optional). Returns an empty string only if creating the folder throws (e.g. a permissions error); it does not check whether the path already exists first.
|
|
111
|
+
Creates a temporary path (optionally as a folder — `createFolder` is required, not optional). Returns an empty string only if creating the folder throws (e.g. a permissions error); it does not check whether the path already exists first. This is the recommended way to create temp files — see [job-patterns.md](job-patterns.md#temp-file-cleanup) for cleanup rules, including why this method should be preferred over ad hoc temp-file approaches like the `tmp` npm package.
|
|
98
112
|
|
|
99
113
|
```ts
|
|
114
|
+
// Switch 24.1+
|
|
100
115
|
flowElement.getFileCount(nested?: boolean): Promise<number>
|
|
101
116
|
```
|
|
102
117
|
Returns the number of files in the active flow. If `nested` is `false`, counts only direct children; if `true` (the default, so it can be omitted), counts recursively (folders themselves are not counted).
|
|
103
118
|
|
|
104
119
|
```ts
|
|
120
|
+
// Switch 24.0+
|
|
105
121
|
flowElement.getScriptDataPath(): string
|
|
106
122
|
```
|
|
107
123
|
Returns the path to the ScriptData folder for this element.
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: http
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 7
|
|
5
|
+
summary: "`HttpRequest` / `HttpResponse` + webhook pattern"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Handling incoming HTTP webhooks"
|
|
8
|
+
---
|
|
9
|
+
|
|
1
10
|
# HttpRequest & HttpResponse Classes
|
|
2
11
|
|
|
3
|
-
Used in the `httpRequestTriggeredSync` and `httpRequestTriggeredAsync` entry points. Webhook subscriptions are registered via `s.httpRequestSubscribe()` in `flowStartTriggered`.
|
|
12
|
+
Used in the `httpRequestTriggeredSync` and `httpRequestTriggeredAsync` entry points. Webhook subscriptions are registered via `s.httpRequestSubscribe()` in `flowStartTriggered`. Both classes, `HttpRequest.Method`, and the subscribe methods need Switch 21.1+.
|
|
4
13
|
|
|
5
14
|
## HttpRequest
|
|
6
15
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: job-patterns
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 16
|
|
5
|
+
summary: "`Job` **rules and gotchas**: file access semantics, temp cleanup, which `sendTo*()` suits which connection type, dataset/child ordering, executor limits"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Writing or reviewing job-handling code. Pair with `job.md` when you also need signatures"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Job Patterns and Behavioral Rules
|
|
11
|
+
|
|
12
|
+
Common behavioral rules for file access, routing, temp file cleanup, and child jobs. These are the most frequent sources of bugs.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## File access semantics
|
|
17
|
+
|
|
18
|
+
`job.get(accessLevel)` and `job.getDataset(name, accessLevel)` behave differently depending on the access level:
|
|
19
|
+
|
|
20
|
+
| Access level | What it returns | Modification |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `AccessLevel.ReadOnly` | Path in the element's **input folder** | Throws if the file is modified |
|
|
23
|
+
| `AccessLevel.ReadWrite` | Path in a **temp location** (copy) | Auto-uploaded to the output folder when `sendTo*()` is called |
|
|
24
|
+
|
|
25
|
+
Always use `AccessLevel.ReadOnly` unless you need to modify the file content.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Temp file cleanup
|
|
30
|
+
|
|
31
|
+
Switch does **not** automatically clean up files you create or pass to job-creation methods. The script is responsible:
|
|
32
|
+
|
|
33
|
+
- Files/folders passed to `flowElement.createJob(path)` — delete after routing.
|
|
34
|
+
- Files/folders passed to `job.createChild(path)` — delete after routing.
|
|
35
|
+
- Files/folders passed to `job.createDataset(name, filePath, model)` — delete after routing.
|
|
36
|
+
|
|
37
|
+
Use `flowElement.createPathWithName()` to create temp files by default — its output lives in the executor's temp area, which Switch removes automatically on executor refresh. Even so, delete explicitly after routing rather than relying on that refresh (see below). `createPathWithName()` needs Switch 24.0+; on an older Switch, build a unique path under `os.tmpdir()` and delete it explicitly after routing.
|
|
38
|
+
|
|
39
|
+
If a temp file is created some other way (e.g. `fs`/`path` writing outside the temp area, or a library that writes its own scratch files), the same rule applies: the script must delete it explicitly. Don't rely on Switch's executor-refresh cleanup for files outside the temp area — it only removes what it put there, so anything else creates a permanent leak.
|
|
40
|
+
|
|
41
|
+
The [`tmp`](https://www.npmjs.com/package/tmp) npm package is common in scripts written by app creators, but it is **not recommended** here — prefer `flowElement.createPathWithName()` instead. If `tmp` is used anyway: its `setGracefulCleanup()` is not always reliable in the Switch script execution environment, so don't depend on it for cleanup — delete the file explicitly after routing regardless. Also pass `discardDescriptor: true` when creating a temp path (e.g. `tmp.fileSync({ discardDescriptor: true })`), or the open file descriptor can leak.
|
|
42
|
+
|
|
43
|
+
> Files accumulate between executor refreshes if not cleaned up. The executor is refreshed after 5 min idle, 5,000 tasks, 150 MB memory, or 1,024 open file handles — at which point accumulated files **created via `createPathWithName()`** are removed automatically, but do not rely on this.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Platform-independent paths
|
|
48
|
+
|
|
49
|
+
Switch Server runs on both Windows and macOS. **App guideline (mandatory for apps, recommended for
|
|
50
|
+
scripts):** a script package should work unmodified on either — paths returned by `job.get()`,
|
|
51
|
+
`flowElement.createPathWithName()`, and similar methods use the host OS's native separator, so avoid
|
|
52
|
+
hardcoding `/` or `\` when building or splitting a path, and don't assume one form when comparing or
|
|
53
|
+
matching paths. Use Node's `path` module (`path.join`, `path.resolve`, `path.sep`, `path.basename`,
|
|
54
|
+
etc.) instead of string concatenation.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Sending jobs
|
|
59
|
+
|
|
60
|
+
After calling any `job.sendTo*()`:
|
|
61
|
+
- Only further `sendTo*()` calls and `job.fail()` are allowed on that job object.
|
|
62
|
+
- An **unmodified** incoming job can be sent to multiple connections (call `sendTo*()` multiple times).
|
|
63
|
+
- A **modified** job (ReadWrite access used) must use `job.createChild()` to produce additional copies for routing to other connections.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// Route unmodified job to two connections — OK
|
|
67
|
+
await job.sendToData(Connection.Level.Success);
|
|
68
|
+
await job.sendToData(Connection.Level.Warning); // only if job was not modified
|
|
69
|
+
|
|
70
|
+
// Route modified content to multiple connections — use createChild
|
|
71
|
+
const child = await job.createChild(tempPath);
|
|
72
|
+
await job.sendTo(conn1);
|
|
73
|
+
await child.sendTo(conn2);
|
|
74
|
+
// Clean up tempPath after routing
|
|
75
|
+
await fs.promises.rm(tempPath);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Consistency with declared connections** (applies to every script, not just apps):
|
|
79
|
+
- If `OutgoingConnections` is not `No`, the script must call some `sendTo*()` method at least once
|
|
80
|
+
for any job it actually routes. A purely timer/event-driven script that never touches a job
|
|
81
|
+
(polling an API, rotating logs, etc.) is a legitimate exception.
|
|
82
|
+
- If `OutgoingConnections="Unlimited"` (more than one outgoing connection allowed), never use
|
|
83
|
+
`job.sendToSingle()` — it only makes sense when exactly one outgoing connection exists.
|
|
84
|
+
- If no output is produced for a job, call `job.sendToNull()` rather than leaving it unrouted.
|
|
85
|
+
- `ConnectionType="Move"` (see [script-declaration.md](../switch-project/script-declaration.md#connectionfields)):
|
|
86
|
+
never use `job.sendToData()`/`job.sendToLog()` — those are for `TrafficLight` connections only. Use
|
|
87
|
+
`job.sendTo()`/`job.sendToSingle()`.
|
|
88
|
+
- `ConnectionType="TrafficLight"`: use only `job.sendToData()`/`job.sendToLog()` — never
|
|
89
|
+
`job.sendTo()`/`job.sendToSingle()`. Use `sendToData` when a Data Success/Warning/Error connection
|
|
90
|
+
is enabled in the declaration, and `sendToLog` when the corresponding Log connection is enabled.
|
|
91
|
+
Conversely, don't declare a Success/Warning/Error element the script never routes to — omit it from
|
|
92
|
+
`ConnectionFields` entirely (see the TrafficLight example in
|
|
93
|
+
[script-declaration.md](../switch-project/script-declaration.md#connectionfields)).
|
|
94
|
+
`sendToData(level)` fails the job if no connected outgoing data connection accepts that level,
|
|
95
|
+
and there is no supported way to check in advance which levels are wired up (see
|
|
96
|
+
[connection.md § Connection.Level enum](connection.md#connectionlevel-enum)). If routing at a
|
|
97
|
+
given level is optional rather than guaranteed by the flow design, expose an explicit custom
|
|
98
|
+
property that lets the flow author opt in or out, and call `job.sendToNull()` when opted out
|
|
99
|
+
instead of guessing whether `sendToData()` will succeed.
|
|
100
|
+
- Prefer `job.fail()`/`flowElement.failProcess()` for genuine processing errors (see
|
|
101
|
+
[logging.md](logging.md)) over silently routing a failed job to a generic error connection —
|
|
102
|
+
reserve an error `TrafficLight` connection for cases the flow author should be able to reroute
|
|
103
|
+
downstream, not as a substitute for `fail()`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Dataset writes must precede child job creation
|
|
108
|
+
|
|
109
|
+
**Known issue — pending a server-side fix; remove this section once fixed.** Applies to all current versions.
|
|
110
|
+
|
|
111
|
+
`job.createChild()` copies the parent's *current* datasets immediately (see [job.md](job.md#child-jobs)); `job.createDataset()` only registers a pending write, flushed to disk at the next `sendTo*()` call (see [job.md](job.md#datasets-metadata)). If a child is created *before* a dataset is written, the child inherits the job's existing dataset under that name instead. At flush time, the pending write fans out concurrently to the parent and to every child created so far, all reading from the same source file on disk — targets that already have a dataset by that name get the file **moved** into place, while targets that don't get it **copied**. When a mix of both happens against a single source file, the move wins the race and the copies fail with errors like `Could not place the file with the decoded data '...' into the datasets folder`, deterministically and on every retry (the source file is gone after the first attempt).
|
|
112
|
+
|
|
113
|
+
**Rule:** perform every dataset write on a job before creating any child of it. Where a script's structure naturally interleaves the two, iterate the dataset outputs first, then the child-job outputs, rather than handling each output in one pass.
|
|
114
|
+
|
|
115
|
+
Approaches that do not work:
|
|
116
|
+
- Deferring `createChild()` until after routing — the SDK rejects any of `createChild`, `setPriority`, `log`, `setPrivateData`, `listDatasets`, `removeDataset`, and others once a `sendTo*()` call has been made on the job, throwing `Method is not allowed at this time. Have you called any sendTo method already?`.
|
|
117
|
+
- Creating children first and only deferring *their* routing — a child is enlisted in the dataset flush at `createChild()` time, not when it's routed.
|
|
118
|
+
- Calling `removeDataset()` before `createDataset()` — this only clears the parent's record and cannot reach copies a child already inherited.
|
|
119
|
+
|
|
120
|
+
A dataset name that's genuinely new to the job never triggers this, since every target takes the copy path — the bug requires a pre-existing dataset of that name.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## processLater
|
|
125
|
+
|
|
126
|
+
`job.processLater()` (Switch 22.1+) defers the job for the next `timerFired` invocation.
|
|
127
|
+
|
|
128
|
+
Constraints:
|
|
129
|
+
- Minimum deferral: **10 seconds** from the time `jobArrived` was called.
|
|
130
|
+
- Cannot be called on jobs created with `createJob()` or `createChild()`.
|
|
131
|
+
- Private data changes made before `processLater()` are preserved.
|
|
132
|
+
- New datasets created before `processLater()` are **discarded**.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Executor cleanup thresholds
|
|
137
|
+
|
|
138
|
+
The Node.js executor process is recycled when any of the following thresholds is reached:
|
|
139
|
+
|
|
140
|
+
| Threshold | Value |
|
|
141
|
+
|---|---|
|
|
142
|
+
| Idle time | 5 minutes |
|
|
143
|
+
| Tasks processed | 5,000 |
|
|
144
|
+
| Memory consumption | 150 MB |
|
|
145
|
+
| Open file handles | 1,024 |
|
|
146
|
+
|
|
147
|
+
On cleanup, any files/folders created in the temp area are removed. Always close file handles and database connections before the entry point returns.
|
|
148
|
+
|
|
149
|
+
See [execution-environment.md](execution-environment.md) for how this process model affects state persistence and error handling across job invocations.
|
|
150
|
+
|
|
151
|
+
## Driving a third-party CLI application
|
|
152
|
+
|
|
153
|
+
`findApplicationPath` and the `ApplicationPath` property are the legacy-scripting mechanism for
|
|
154
|
+
locating a third-party application; neither is available to Node.js scripts, and `ApplicationPath`
|
|
155
|
+
is a reserved name Switch silently drops from the declaration (see
|
|
156
|
+
[script-declaration.md](../switch-project/script-declaration.md#built-in-elementfields)). A Node.js script or
|
|
157
|
+
app that shells out to an external binary resolves the path itself, using the following pattern.
|
|
158
|
+
|
|
159
|
+
1. Declare an ordinary custom property for the path under a name of your own — `cliPath`, not
|
|
160
|
+
`ApplicationPath`.
|
|
161
|
+
2. Give it the editor chain `automatic;choosefile;sltextwithvar;scriptexp`, with
|
|
162
|
+
`Default="Automatic"` and `Subtype="automatic"`. The `automatic` literal editor is what lets the
|
|
163
|
+
user express "find it yourself" without typing a magic string, and `getPropertyStringValue()`
|
|
164
|
+
returns the literal `"Automatic"` for it — see
|
|
165
|
+
[property-editors.md § Literal editors](../switch-project/property-editors.md#literal-editors).
|
|
166
|
+
3. Resolve the path once per flow start, in `flowStartTriggered`: if the property reads
|
|
167
|
+
`Automatic`, run whatever discovery the application needs; otherwise take
|
|
168
|
+
`flowElement.getPropertyStringValue('cliPath')`. Verify the resulting path actually exists, then
|
|
169
|
+
store it in global data at `Scope.FlowElement`. If it doesn't exist, store an empty string
|
|
170
|
+
rather than leaving the tag unset.
|
|
171
|
+
4. In `jobArrived`/`timerFired`, read the path back from global data. An empty string means
|
|
172
|
+
discovery failed: call `flowElement.failProcess()` so the element goes into an error state,
|
|
173
|
+
instead of failing every individual job.
|
|
174
|
+
5. Remove the global data entry in `flowStopTriggered` — global data is never cleaned up
|
|
175
|
+
automatically (see [switch.md § Global data](switch.md#global-data)).
|
|
176
|
+
|
|
177
|
+
Doing discovery once at flow start rather than per job keeps a filesystem search off the job path,
|
|
178
|
+
and gives the operator a single clear element-level error when the application is missing.
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: job
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 5
|
|
5
|
+
summary: "`Job` **signatures**: routing, file access, child jobs, private data, datasets"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Looking up what a `job.*` method takes, returns, or throws"
|
|
8
|
+
---
|
|
9
|
+
|
|
1
10
|
# Job Class
|
|
2
11
|
|
|
3
12
|
A `Job` represents a file or folder moving through the flow. It is passed to `jobArrived` and can be created via `flowElement.createJob()` or `job.createChild()`. Every job must be routed with a `sendTo*()` call or `fail()` before the entry point ends (or at a later time via `timerFired`).
|
|
@@ -22,6 +31,7 @@ job.isFolder(): boolean
|
|
|
22
31
|
## Priority
|
|
23
32
|
|
|
24
33
|
```ts
|
|
34
|
+
// Switch 22.1+
|
|
25
35
|
job.getPriority(): number
|
|
26
36
|
job.setPriority(priority: number): void
|
|
27
37
|
```
|
|
@@ -36,7 +46,7 @@ Returns the local filesystem path to the job. Use `AccessLevel.ReadOnly` to read
|
|
|
36
46
|
|
|
37
47
|
## Routing
|
|
38
48
|
|
|
39
|
-
Every job must be routed exactly once.
|
|
49
|
+
Every job must be routed exactly once. See [job-patterns.md § Sending jobs](job-patterns.md#sending-jobs) for which `sendTo*()` method is appropriate for a given `ConnectionType`/`OutgoingConnections` declaration.
|
|
40
50
|
|
|
41
51
|
```ts
|
|
42
52
|
job.sendToNull(): Promise<void>
|
|
@@ -56,7 +66,7 @@ Send to a specific connection (any type). Get connections via `flowElement.getOu
|
|
|
56
66
|
```ts
|
|
57
67
|
job.sendToData(level: Connection.Level, newName?: string): Promise<void>
|
|
58
68
|
```
|
|
59
|
-
Send via a traffic light "data" connection at the specified level. Fails the job if no matching connection exists.
|
|
69
|
+
Send via a traffic light "data" connection at the specified level. Fails the job if no matching connection exists. **There is no supported way to check in advance whether a connection at that level exists** — don't call `connection.getPropertyStringValue("Success")`/`"Warning"`/`"Error"` expecting to detect this; that throws an invalid-tag error, since traffic light levels aren't a readable connection property. See [job-patterns.md § Sending jobs](job-patterns.md#sending-jobs) for the pattern (an explicit custom property plus `job.sendToNull()`).
|
|
60
70
|
|
|
61
71
|
```ts
|
|
62
72
|
job.sendToLog(level: Connection.Level, model: DatasetModel, newName?: string): Promise<void>
|
|
@@ -64,23 +74,25 @@ job.sendToLog(level: Connection.Level, model: DatasetModel, newName?: string): P
|
|
|
64
74
|
Send via a traffic light "log" connection. For "data with log" connections, attaches the job as a metadata dataset to data jobs routed via `sendToData`. Discards the job if no matching connection exists.
|
|
65
75
|
|
|
66
76
|
```ts
|
|
77
|
+
// Switch 22.1+
|
|
67
78
|
job.sendToChannel(channelId: string, newName?: string): Promise<void>
|
|
68
79
|
```
|
|
69
80
|
Send to a named channel. Unlike `sendToData`/`sendToLog`, this **throws** synchronously (it does not fail-and-route the job) if the channel has no active subscriber, or if `channelId`/`newName` is empty — catch it explicitly.
|
|
70
81
|
|
|
71
82
|
```ts
|
|
83
|
+
// Switch 22.1+
|
|
72
84
|
job.processLater(seconds?: number): Promise<void> // default: 300
|
|
73
85
|
```
|
|
74
86
|
Re-queue the job for `jobArrived` after a minimum delay (re-evaluates dynamic properties). Minimum delay: 10 seconds from `jobArrived` (a warning is logged if less). Cannot be called on newly created or child jobs. Content is not modified; newly created datasets are discarded. Private data changes are preserved.
|
|
75
87
|
|
|
76
88
|
## Failure & logging
|
|
77
89
|
|
|
78
|
-
See [
|
|
90
|
+
See [logging.md](logging.md) for log level semantics and logging practice.
|
|
79
91
|
|
|
80
92
|
```ts
|
|
81
93
|
job.fail(message: string, messageParams?: (string | number | boolean)[]): void
|
|
82
94
|
```
|
|
83
|
-
Log a fatal error and move the job to Problem Jobs. Newly created jobs are not moved. Use `%1`, `%2`, etc. for substitution.
|
|
95
|
+
Log a fatal error and move the job to Problem Jobs. Newly created jobs are not moved. Use `%1`, `%2`, etc. for substitution. When targeting Switch 20.x, always pass `messageParams`, even as `[]` (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)).
|
|
84
96
|
|
|
85
97
|
```ts
|
|
86
98
|
job.log(level: LogLevel, message: string, messageParams?: (string | number | boolean)[]): Promise<void>
|
|
@@ -92,11 +104,11 @@ Log a message including job context. To log a literal `%`, pass it as a param: `
|
|
|
92
104
|
```ts
|
|
93
105
|
job.createChild(path: string): Promise<Job>
|
|
94
106
|
```
|
|
95
|
-
Creates a new job inheriting the processing history, metadata, and private data of the parent. The caller is responsible for cleaning up the source file after routing.
|
|
107
|
+
Creates a new job inheriting the processing history, metadata, and private data of the parent. Takes effect immediately: the child's datasets are copied from the parent's *current* server-side state, so any pending `createDataset()` write not yet flushed by a `sendTo*()` call is not reflected in it. See [job-patterns.md](job-patterns.md#dataset-writes-must-precede-child-job-creation) — creating a child before writing a dataset is a common source of bugs. The caller is responsible for cleaning up the source file after routing (see [job-patterns.md](job-patterns.md#temp-file-cleanup)).
|
|
96
108
|
|
|
97
109
|
## Private data
|
|
98
110
|
|
|
99
|
-
Private data is arbitrary key/value storage attached to a job and passed along with it.
|
|
111
|
+
Private data is arbitrary key/value storage attached to a job and passed along with it. `EnfocusSwitchPrivateDataTag` needs Switch 21.0+; before 21.0, store string values only (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)).
|
|
100
112
|
|
|
101
113
|
```ts
|
|
102
114
|
job.getPrivateData(tag: string | EnfocusSwitchPrivateDataTag): Promise<any>
|
|
@@ -123,12 +135,12 @@ Datasets are named files (XML, XMP, JDF, or Opaque) attached to a job.
|
|
|
123
135
|
```ts
|
|
124
136
|
job.listDatasets(): Promise<{ name: string, model: DatasetModel, extension: string }[]>
|
|
125
137
|
```
|
|
126
|
-
List all datasets attached to the job.
|
|
138
|
+
List all datasets attached to the job. **Immediate**: reads server state at once, so a pending `createDataset()` write not yet flushed by `sendTo*()` will not appear.
|
|
127
139
|
|
|
128
140
|
```ts
|
|
129
141
|
job.createDataset(name: string, filePath: string, model: DatasetModel): Promise<void>
|
|
130
142
|
```
|
|
131
|
-
Attach a new dataset.
|
|
143
|
+
Attach a new dataset. **Deferred**: registers in-memory only; the file is not uploaded until the next `sendTo*()` call. Caller must clean up the source file after routing — see [job-patterns.md](job-patterns.md#temp-file-cleanup).
|
|
132
144
|
|
|
133
145
|
```ts
|
|
134
146
|
job.getDataset(name: string, accessLevel: AccessLevel): Promise<string>
|
|
@@ -138,10 +150,12 @@ Returns the local path to the dataset file. Prefer `AccessLevel.ReadOnly`.
|
|
|
138
150
|
```ts
|
|
139
151
|
job.removeDataset(name: string): Promise<void>
|
|
140
152
|
```
|
|
141
|
-
Remove a dataset by name. Throws if it does not exist.
|
|
153
|
+
Remove a dataset by name. **Immediate**: updates server state at once. Throws if it does not exist.
|
|
142
154
|
|
|
143
155
|
## Variables & structured data
|
|
144
156
|
|
|
157
|
+
Everything in this section needs Switch 24.0+.
|
|
158
|
+
|
|
145
159
|
```ts
|
|
146
160
|
job.getVariableAsString(variable: string): Promise<string>
|
|
147
161
|
```
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: logging
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 18
|
|
5
|
+
summary: "Log level semantics, logging practice, `console.log` limitation, common gotchas"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Adding or reviewing `job.log()`/`flowElement.log()`/`job.fail()`/`failProcess()` calls"
|
|
8
|
+
---
|
|
9
|
+
|
|
1
10
|
# Logging Practices
|
|
2
11
|
|
|
3
12
|
Guidance for using `job.log()`, `flowElement.log()`, `job.fail()`, and `flowElement.failProcess()`
|
|
4
|
-
— see [
|
|
5
|
-
[
|
|
6
|
-
listed in [
|
|
13
|
+
— see [job.md](job.md#failure--logging) and
|
|
14
|
+
[flow-element.md](flow-element.md#logging) for their signatures. `LogLevel` values are
|
|
15
|
+
listed in [enums.md](enums.md). Each log call has a small performance overhead, so where
|
|
7
16
|
and how often to log is a deliberate choice, not a default.
|
|
8
17
|
|
|
9
18
|
## Log levels
|
|
@@ -13,7 +22,7 @@ and how often to log is a deliberate choice, not a default.
|
|
|
13
22
|
| `Error` | The job/step failed or produced a wrong/unusable result. Often accompanies or precedes a hard stop, but can stand alone if the job continues in a degraded way. |
|
|
14
23
|
| `Warning` | Something unexpected happened but processing continued successfully (a fallback was used, an optional resource was missing). |
|
|
15
24
|
| `Info` | Normal, expected checkpoints worth surfacing to a flow operator without them needing debug mode (e.g. "processed 40 records", "output written to X"). |
|
|
16
|
-
| `Debug` | Verbose detail for diagnosing execution. Many users leave debug logging enabled in production to self-diagnose issues and to help app developers and Enfocus Support — leave meaningful checkpoints in, but trim noisy or no-longer-useful debug logs before shipping. **`Debug` messages only reach the log database if the Switch preference "Log debug messages" is turned on** (off by default) — see [
|
|
25
|
+
| `Debug` | Verbose detail for diagnosing execution. Many users leave debug logging enabled in production to self-diagnose issues and to help app developers and Enfocus Support — leave meaningful checkpoints in, but trim noisy or no-longer-useful debug logs before shipping. **`Debug` messages only reach the log database if the Switch preference "Log debug messages" is turned on** (off by default) — see [logs-and-dataroot.md](../switch-project/logs-and-dataroot.md#retention-and-the-debug-level-gate). |
|
|
17
26
|
|
|
18
27
|
## Logging in loops
|
|
19
28
|
|
|
@@ -24,7 +33,7 @@ one call per iteration, e.g. `"Found 12 matching jobs"` instead of one log line
|
|
|
24
33
|
## `console.log` does not work
|
|
25
34
|
|
|
26
35
|
`console.log` only produces output when a debug session is attached (see
|
|
27
|
-
[
|
|
36
|
+
[debugging.md](../switch-project/debugging.md)); in a normal run it is not visible anywhere. Never rely on it
|
|
28
37
|
in production scripts — use `job.log()`/`flowElement.log()` for anything that should be visible in
|
|
29
38
|
the log/message pane.
|
|
30
39
|
|
|
@@ -51,3 +60,36 @@ job.log(LogLevel.Info, '%1', [urlThatMightContainPercent]);
|
|
|
51
60
|
`job.fail(message, messageParams?: (string | number | boolean)[])` takes an **array**.
|
|
52
61
|
`flowElement.failProcess(message, messageParam?: string | number | boolean)` takes a **single
|
|
53
62
|
value**, not an array. Passing an array to `failProcess` is a common mistake.
|
|
63
|
+
|
|
64
|
+
## Placeholders instead of concatenation
|
|
65
|
+
|
|
66
|
+
Build log messages with `%1`, `%2`, … placeholders and pass the dynamic values as
|
|
67
|
+
`messageParams`/`messageParam`, rather than concatenating or interpolating them into the message
|
|
68
|
+
string (beyond the literal-`%` escaping case above). A concatenated message can't be translated
|
|
69
|
+
correctly — word order and pluralization vary by language, so a translator needs the message
|
|
70
|
+
template and the substituted values kept separate. This matters even for a script that only ships in
|
|
71
|
+
English today if it might ever be localized later.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Avoid — can't be translated correctly, and hits the literal-% gotcha if fileName contains one
|
|
75
|
+
job.log(LogLevel.Info, `Processed file ${fileName} in ${seconds}s`);
|
|
76
|
+
|
|
77
|
+
// Prefer
|
|
78
|
+
job.log(LogLevel.Info, 'Processed file %1 in %2s', [fileName, seconds]);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Message quality
|
|
82
|
+
|
|
83
|
+
- Log messages are user-facing text — proofread them. A message with grammatical errors or typos
|
|
84
|
+
looks unfinished and undermines trust.
|
|
85
|
+
- Log an `Error` when a custom `validateProperties`/`validateConnectionProperties` check returns
|
|
86
|
+
`valid: false` for a tag, and when `getLibraryForProperty`/`getLibraryForConnectionProperty` would
|
|
87
|
+
otherwise return an empty list — see [entry-points.md § Property UI callbacks](entry-points.md#property-ui-callbacks).
|
|
88
|
+
Silently returning `false`/`[]` with no log line leaves the user without any explanation.
|
|
89
|
+
|
|
90
|
+
## Log volume
|
|
91
|
+
|
|
92
|
+
**App guideline (mandatory for apps, recommended for scripts):** don't over-log. Beyond the
|
|
93
|
+
[Logging in loops](#logging-in-loops) guidance, avoid `Info`/`Debug` calls that don't help a flow
|
|
94
|
+
operator or support engineer understand what happened — every extra message adds noise (and
|
|
95
|
+
translation burden, for an app) without adding signal.
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: switch
|
|
3
|
+
category: switch-api
|
|
4
|
+
order: 3
|
|
5
|
+
summary: "`Switch` (`s`): global data, webhooks, abort, server utilities"
|
|
6
|
+
triggers:
|
|
7
|
+
- "Using global data, webhooks, abort, or server settings"
|
|
8
|
+
---
|
|
9
|
+
|
|
1
10
|
# Switch Class
|
|
2
11
|
|
|
3
12
|
The `Switch` instance `s` is passed to every entry point. It provides global data storage, webhook subscription, abort handling, and server utilities.
|
|
@@ -9,12 +18,12 @@ Global data is key/value storage shared across entry point invocations, scoped b
|
|
|
9
18
|
| Scope | Shared across |
|
|
10
19
|
|---|---|
|
|
11
20
|
| `Scope.FlowElement` | This script element instance only |
|
|
12
|
-
| `Scope.FlowElements` | All instances of the same script in the same flow |
|
|
21
|
+
| `Scope.FlowElements` | All instances of the same script in the same flow (Switch 22.0+) |
|
|
13
22
|
| `Scope.Element` | All instances of the same script across all flows |
|
|
14
|
-
| `Scope.Flow` | All elements in the same flow |
|
|
23
|
+
| `Scope.Flow` | All elements in the same flow (Switch 22.0+) |
|
|
15
24
|
| `Scope.Global` | Any element in any flow — use sparingly; prefix tags with your company name |
|
|
16
25
|
|
|
17
|
-
Dates stored via `setGlobalData` are returned as strings by `getGlobalData` — parse them in your script.
|
|
26
|
+
Dates stored via `setGlobalData` are returned as strings by `getGlobalData` — parse them in your script. Before Switch 21.0, store string values only (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)).
|
|
18
27
|
|
|
19
28
|
```ts
|
|
20
29
|
s.getGlobalData(scope: Scope, tag: string, lock?: boolean): Promise<any>
|
|
@@ -40,6 +49,8 @@ Delete one or multiple global data entries.
|
|
|
40
49
|
|
|
41
50
|
## Webhooks
|
|
42
51
|
|
|
52
|
+
Both methods need Switch 21.1+.
|
|
53
|
+
|
|
43
54
|
```ts
|
|
44
55
|
s.httpRequestSubscribe(method: HttpRequest.Method, path: string, args: any[]): Promise<void>
|
|
45
56
|
```
|
|
@@ -53,6 +64,7 @@ Unsubscribe a previously registered webhook.
|
|
|
53
64
|
## Abort
|
|
54
65
|
|
|
55
66
|
```ts
|
|
67
|
+
// Switch 22.1+
|
|
56
68
|
s.setAbortData(abortData: any): void
|
|
57
69
|
```
|
|
58
70
|
Store a value that will be passed to the `abort` entry point when the configured abort timeout expires.
|
|
@@ -60,16 +72,68 @@ Store a value that will be passed to the `abort` entry point when the configured
|
|
|
60
72
|
## Server utilities
|
|
61
73
|
|
|
62
74
|
```ts
|
|
75
|
+
// Switch 24.0+
|
|
63
76
|
s.getPreferenceSetting(settingKey: string): Promise<any>
|
|
64
77
|
```
|
|
65
78
|
Retrieve a Switch preference setting. `settingKey` format: `"settingGroup"` or `"settingGroup/settingName"`. Returns `''` if the setting/group doesn't exist. Any field whose name contains `password`, `pwd`, or `secret` is redacted in the result. The returned value is JSON-stringified, not a raw object.
|
|
66
79
|
|
|
67
80
|
```ts
|
|
81
|
+
// Switch 24.0+
|
|
68
82
|
s.getServerVersion(): number
|
|
69
83
|
```
|
|
70
|
-
Returns the Switch server version as `majorVersion + updateNumber/100` (e.g. Switch 25.11 → `25.11`), not a literal version string — don't parse it as one.
|
|
84
|
+
Returns the Switch server version as `majorVersion + updateNumber/100` (e.g. Switch 25.11 → `25.11`), not a literal version string — don't parse it as one. The method doesn't exist before Switch 24.0, so it can't detect older versions; see [api-versions.md § Checking at runtime](api-versions.md#checking-at-runtime).
|
|
71
85
|
|
|
72
86
|
```ts
|
|
87
|
+
// Switch 22.0+
|
|
73
88
|
Switch.tr(str: string): string
|
|
74
89
|
```
|
|
75
90
|
Static method. Marks a string literal for translation (used by SwitchScriptTool). Returns the string unchanged at runtime.
|
|
91
|
+
|
|
92
|
+
### Translation extraction rules
|
|
93
|
+
|
|
94
|
+
`SwitchScriptTool --generate-translations` extracts strings by **static analysis of the source
|
|
95
|
+
text**, not by running it. It only ever sees a `Switch.tr(...)` call whose argument is a string
|
|
96
|
+
literal, spelled exactly like that. Anything it can't resolve statically is silently skipped — no
|
|
97
|
+
error, no warning, just a missing entry in the `.ts` file. Always check the generated `.ts` for the
|
|
98
|
+
strings you expected.
|
|
99
|
+
|
|
100
|
+
What extraction handles:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
Switch.tr('single'); // single, double and backtick quotes all work
|
|
104
|
+
Switch.tr(`multi
|
|
105
|
+
line`); // multi-line literals work
|
|
106
|
+
Switch.tr('one ' + 'two '); // concatenation of literals works
|
|
107
|
+
// The tr() call doesn't have to sit at the point of use:
|
|
108
|
+
const s = Switch.tr('Predefined string');
|
|
109
|
+
await job.log(LogLevel.Info, s); // extracted, and translated at runtime
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
What it does **not** handle:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const t = Switch.tr; // aliasing the function: never extracted
|
|
116
|
+
await job.log(LogLevel.Info, t('nope'));
|
|
117
|
+
|
|
118
|
+
const token = getToken();
|
|
119
|
+
Switch.tr('prefix ' + token); // concatenation with a non-literal
|
|
120
|
+
Switch.tr(`prefix ${token}`); // template interpolation
|
|
121
|
+
Switch.tr('Job ID = ' + job.getId());
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For dynamic values, mark the template and pass the values as `messageParams` — the template gets
|
|
125
|
+
translated, the substituted values don't:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
await job.log(LogLevel.Info, Switch.tr('Job ID = %1'), [job.getId()]);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Arguments can themselves be translated by wrapping them separately:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const mode = readOnly ? Switch.tr('Read-only') : Switch.tr('Regular');
|
|
135
|
+
await job.log(LogLevel.Info, Switch.tr('Job ID = %1, Mode = %2'), [job.getId(), mode]);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
See [logging.md § Placeholders instead of concatenation](logging.md#placeholders-instead-of-concatenation)
|
|
139
|
+
for the same rule stated from the logging side.
|