@plitzi/sdk-server 0.35.2 → 0.35.4
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 +30 -0
- package/dist/actions.d.ts +2 -1
- package/dist/modules/actions/index.d.ts +2 -1
- package/dist/modules/actions/index.js +4 -1
- package/dist/modules/actions/moduleFor.js +1 -0
- package/dist/modules/actions/runtime/runAction.js +8 -1
- package/dist/modules/actions/tasks/builtins.d.ts +3 -2
- package/dist/modules/actions/tasks/builtins.js +3 -2
- package/dist/modules/actions/tasks/email.d.ts +2 -0
- package/dist/modules/actions/tasks/email.js +62 -0
- package/dist/modules/actions/tasks/email.test.d.ts +1 -0
- package/dist/modules/actions/tasks/registry.d.ts +8 -1
- package/dist/modules/actions/tasks/registry.js +12 -3
- package/dist/modules/actions/types.d.ts +6 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @plitzi/sdk-server
|
|
2
2
|
|
|
3
|
+
## 0.35.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- v0.35.4
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @plitzi/plitzi-sdk@0.35.4
|
|
10
|
+
- @plitzi/sdk-shared@0.35.4
|
|
11
|
+
|
|
12
|
+
## 0.35.3
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 0211dc4: A form speaks the site's language when it refuses a value.
|
|
17
|
+
|
|
18
|
+
- **Every rule of a `formControl` can say what it wants to say.** `requiredMessage`, `minLengthMessage`,
|
|
19
|
+
`maxLengthMessage` and `formatMessage` join `patternMessage` and `matchesMessage`; left empty, each falls back to
|
|
20
|
+
the English sentence it said before. `formatMessage` is said when the value does not have the shape the control's
|
|
21
|
+
type asks for — an address, for an `email` — one attribute for every type that has a shape. Offered in the builder under the rule they belong to.
|
|
22
|
+
- **A `form` can turn the browser's own checks off (`noValidate`, "Skip Browser Validation" in the builder).** Left on
|
|
23
|
+
— the default, as before — the browser answers first for a blank required field and a malformed address, in a
|
|
24
|
+
bubble no style reaches and in the browser's language, while every other rule answers under the control. Turned
|
|
25
|
+
on, the form's rules are the only ones, and all of them answer under the control.
|
|
26
|
+
- **An `email` control checks the address itself,** by the same definition the browser uses, so the format is still
|
|
27
|
+
asked for when the browser's checks are off.
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [0211dc4]
|
|
30
|
+
- @plitzi/plitzi-sdk@0.35.3
|
|
31
|
+
- @plitzi/sdk-shared@0.35.3
|
|
32
|
+
|
|
3
33
|
## 0.35.2
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/actions.d.ts
CHANGED
|
@@ -55,7 +55,8 @@ export { createKvStore } from './modules/actions/runtime/kvStore';
|
|
|
55
55
|
* and still wants refusals on the log stream wires one of each; that is the normal shape, not a workaround.
|
|
56
56
|
*/
|
|
57
57
|
export { createRunLogger, createRejectLogger } from './modules/actions/runtime/runLogger';
|
|
58
|
-
export type {
|
|
58
|
+
export type { TaskRegistryOptions } from './modules/actions/tasks/registry';
|
|
59
|
+
export type { ActionCredential, ActionDbDriver, ActionEmailAdapter, ActionEmailMessage, ActionKvAdapter, ActionKvStore, ActionLookups, ActionRejectRecord, ActionRunRecord, ActionRunRequest, ActionRunResult, ActionsConfig, ActionTask, ActionTaskContext, ActionTaskRegistry, RegisteredTask, ResolvedConnector } from './modules/actions/types';
|
|
59
60
|
export type { ActionsModule } from './modules/actions';
|
|
60
61
|
export type { ActiveRun, RunGuards } from './modules/actions/runtime/guards';
|
|
61
62
|
export type { ScheduleResult, ScheduleRunner, ScheduleTick } from './modules/actions/runtime/schedule';
|
|
@@ -37,4 +37,5 @@ export { handleActionCancel } from './transport/cancelHandler';
|
|
|
37
37
|
export type { ActiveRun, RunGuards } from './runtime/guards';
|
|
38
38
|
export type { ScheduleResult, ScheduleRunner, ScheduleTick } from './runtime/schedule';
|
|
39
39
|
export type { ActionTaskDescriptor } from './taskCatalog';
|
|
40
|
-
export type {
|
|
40
|
+
export type { TaskRegistryOptions } from './tasks/registry';
|
|
41
|
+
export type { ActionCredential, ActionDbDriver, ActionEmailAdapter, ActionEmailMessage, ActionKvStore, ActionLookups, ActionRejectRecord, ActionRunRecord, ActionRunRequest, ActionRunResult, ActionsConfig, ActionTask, ActionTaskContext, ActionTaskRegistry, RegisteredTask, ResolvedConnector } from './types';
|
|
@@ -25,7 +25,10 @@ import { handleActionCancel } from "./transport/cancelHandler.js";
|
|
|
25
25
|
* the same as having no single-flight at all. Nothing outside this folder needs to know how a run is assembled.
|
|
26
26
|
*/
|
|
27
27
|
var createActionsModule = (config) => {
|
|
28
|
-
const registry = createTaskRegistry(config.tasks,
|
|
28
|
+
const registry = createTaskRegistry(config.tasks, {
|
|
29
|
+
db: (config.dbDrivers?.length ?? 0) > 0,
|
|
30
|
+
email: config.email !== void 0
|
|
31
|
+
});
|
|
29
32
|
const { runAction } = createActionRunner(config, registry, config.fetchImpl);
|
|
30
33
|
/**
|
|
31
34
|
* Single-flight over the store the deployment already gave the `kv` tasks — its own Redis, table or whatever it
|
|
@@ -26,6 +26,7 @@ var actionsModuleFor = (config) => {
|
|
|
26
26
|
rateLimit: config.action?.rateLimit,
|
|
27
27
|
idempotency: config.action?.idempotency,
|
|
28
28
|
dbDrivers: config.action?.dbDrivers,
|
|
29
|
+
email: config.action?.email,
|
|
29
30
|
onRun: config.action?.onRun
|
|
30
31
|
});
|
|
31
32
|
modules.set(config, module);
|
|
@@ -260,6 +260,7 @@ var createActionRunner = (config, registry, baseFetch = fetch) => {
|
|
|
260
260
|
fetch: runFetch,
|
|
261
261
|
kv: scopedKv,
|
|
262
262
|
dbDrivers: config.dbDrivers ?? [],
|
|
263
|
+
email: config.email,
|
|
263
264
|
emit: (chunk) => request.emit?.(redact(chunk))
|
|
264
265
|
});
|
|
265
266
|
const trace = [];
|
|
@@ -280,7 +281,13 @@ var createActionRunner = (config, registry, baseFetch = fetch) => {
|
|
|
280
281
|
spaceId: request.spaceId,
|
|
281
282
|
environment: request.environment,
|
|
282
283
|
trigger: request.trigger,
|
|
283
|
-
runId
|
|
284
|
+
runId,
|
|
285
|
+
/**
|
|
286
|
+
* The moment the run started, as an ISO string — and the same moment for every step. A check that asks the
|
|
287
|
+
* time and the write it guards cannot straddle midnight, and a template reads it in the zone it cares about
|
|
288
|
+
* with `{{ now|date('Y-m-d', 'Europe/Madrid') }}`.
|
|
289
|
+
*/
|
|
290
|
+
now: new Date(startedAt).toISOString()
|
|
284
291
|
};
|
|
285
292
|
let status = "completed";
|
|
286
293
|
let returned;
|
|
@@ -2,7 +2,8 @@ import { ActionTask } from '../types';
|
|
|
2
2
|
/**
|
|
3
3
|
* The tasks `sdk-server` itself ships: the ones that are mechanism rather than policy.
|
|
4
4
|
*
|
|
5
|
-
* Anything needing an account somewhere —
|
|
6
|
-
* `actions.tasks`.
|
|
5
|
+
* Anything needing an account somewhere — AI, object storage — is the DEPLOYMENT's, registered through
|
|
6
|
+
* `actions.tasks`. `db.query` and `email.send` sit in between: the task is shipped because its rules are the same
|
|
7
|
+
* everywhere, the driver or transport is the deployment's, and the registry offers the task only when one was given.
|
|
7
8
|
*/
|
|
8
9
|
export declare const builtinTasks: ActionTask<Record<string, unknown>>[];
|
|
@@ -8,8 +8,9 @@ import { transformTasks } from "./transform.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* The tasks `sdk-server` itself ships: the ones that are mechanism rather than policy.
|
|
10
10
|
*
|
|
11
|
-
* Anything needing an account somewhere —
|
|
12
|
-
* `actions.tasks`.
|
|
11
|
+
* Anything needing an account somewhere — AI, object storage — is the DEPLOYMENT's, registered through
|
|
12
|
+
* `actions.tasks`. `db.query` and `email.send` sit in between: the task is shipped because its rules are the same
|
|
13
|
+
* everywhere, the driver or transport is the deployment's, and the registry offers the task only when one was given.
|
|
13
14
|
*/
|
|
14
15
|
var builtinTasks = [
|
|
15
16
|
...flowTasks,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/modules/actions/tasks/email.ts
|
|
2
|
+
/**
|
|
3
|
+
* One address, and nothing a mail header could be split on.
|
|
4
|
+
*
|
|
5
|
+
* Stricter than the standard on purpose: a comma or a semicolon is how one recipient becomes a hundred, and a line
|
|
6
|
+
* break is how a subject becomes a second header. A flow renders what a visitor typed, so this is where that stops.
|
|
7
|
+
*/
|
|
8
|
+
var ADDRESS = /^[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+$/;
|
|
9
|
+
var MAX_SUBJECT = 200;
|
|
10
|
+
var MAX_TEXT = 2e4;
|
|
11
|
+
var emailTasks = [{
|
|
12
|
+
namespace: "email",
|
|
13
|
+
action: "send",
|
|
14
|
+
title: "Send Email",
|
|
15
|
+
description: "Sends a plain-text message through the transport this deployment configured",
|
|
16
|
+
params: {
|
|
17
|
+
to: {
|
|
18
|
+
type: "text",
|
|
19
|
+
canBind: true,
|
|
20
|
+
defaultValue: "",
|
|
21
|
+
label: "To"
|
|
22
|
+
},
|
|
23
|
+
subject: {
|
|
24
|
+
type: "text",
|
|
25
|
+
canBind: true,
|
|
26
|
+
defaultValue: "",
|
|
27
|
+
label: "Subject"
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
type: "codemirror-text",
|
|
31
|
+
canBind: true,
|
|
32
|
+
defaultValue: "",
|
|
33
|
+
label: "Text"
|
|
34
|
+
},
|
|
35
|
+
replyTo: {
|
|
36
|
+
type: "text",
|
|
37
|
+
canBind: true,
|
|
38
|
+
defaultValue: "",
|
|
39
|
+
label: "Reply to"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
run: async ({ to, subject, text, replyTo }, ctx) => {
|
|
43
|
+
if (!ctx.email) throw new Error("This server has no email transport");
|
|
44
|
+
const recipient = to.trim();
|
|
45
|
+
if (!ADDRESS.test(recipient)) throw new Error("The recipient is not one email address");
|
|
46
|
+
const answerTo = replyTo.trim();
|
|
47
|
+
if (answerTo !== "" && !ADDRESS.test(answerTo)) throw new Error("The reply-to is not one email address");
|
|
48
|
+
const title = subject.trim();
|
|
49
|
+
if (title === "" || /[\r\n]/.test(title) || title.length > MAX_SUBJECT) throw new Error(`The subject must be one line of 1 to ${MAX_SUBJECT} characters`);
|
|
50
|
+
if (text.trim() === "" || text.length > MAX_TEXT) throw new Error(`The text must have 1 to ${MAX_TEXT} characters`);
|
|
51
|
+
await ctx.email.send({
|
|
52
|
+
spaceId: ctx.spaceId,
|
|
53
|
+
to: recipient,
|
|
54
|
+
subject: title,
|
|
55
|
+
text,
|
|
56
|
+
...answerTo === "" ? {} : { replyTo: answerTo }
|
|
57
|
+
});
|
|
58
|
+
return { sent: true };
|
|
59
|
+
}
|
|
60
|
+
}];
|
|
61
|
+
//#endregion
|
|
62
|
+
export { emailTasks };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ActionTask, ActionTaskRegistry } from '../types';
|
|
2
2
|
export declare const taskName: (task: Pick<ActionTask, "namespace" | "action">) => string;
|
|
3
|
+
/** The shipped tasks that are only real when the deployment supplied what they run through. */
|
|
4
|
+
export type TaskRegistryOptions = {
|
|
5
|
+
/** At least one database driver is registered, so `db.query` has an engine to run against. */
|
|
6
|
+
db?: boolean;
|
|
7
|
+
/** An email transport is configured, so `email.send` has somewhere to hand a message. */
|
|
8
|
+
email?: boolean;
|
|
9
|
+
};
|
|
3
10
|
/**
|
|
4
11
|
* Builds the set of tasks this server can run.
|
|
5
12
|
*
|
|
@@ -7,4 +14,4 @@ export declare const taskName: (task: Pick<ActionTask, "namespace" | "action">)
|
|
|
7
14
|
* name is unreachable from any document, and one shadowing a built-in silently changes what every existing action
|
|
8
15
|
* in that deployment does. Both are invisible until a run misbehaves in production.
|
|
9
16
|
*/
|
|
10
|
-
export declare const createTaskRegistry: (custom?: ActionTask<never>[],
|
|
17
|
+
export declare const createTaskRegistry: (custom?: ActionTask<never>[], { db, email }?: TaskRegistryOptions) => ActionTaskRegistry;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { builtinTasks } from "./builtins.js";
|
|
2
2
|
import { dbTasks } from "./db.js";
|
|
3
|
+
import { emailTasks } from "./email.js";
|
|
3
4
|
//#region src/modules/actions/tasks/registry.ts
|
|
4
5
|
var NAME_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
|
5
6
|
var taskName = (task) => `${task.namespace}.${task.action}`;
|
|
6
|
-
var reservedNamespaces = new Set([
|
|
7
|
+
var reservedNamespaces = new Set([
|
|
8
|
+
...builtinTasks,
|
|
9
|
+
...dbTasks,
|
|
10
|
+
...emailTasks
|
|
11
|
+
].map((task) => task.namespace));
|
|
7
12
|
/**
|
|
8
13
|
* Builds the set of tasks this server can run.
|
|
9
14
|
*
|
|
@@ -11,9 +16,13 @@ var reservedNamespaces = new Set([...builtinTasks, ...dbTasks].map((task) => tas
|
|
|
11
16
|
* name is unreachable from any document, and one shadowing a built-in silently changes what every existing action
|
|
12
17
|
* in that deployment does. Both are invisible until a run misbehaves in production.
|
|
13
18
|
*/
|
|
14
|
-
var createTaskRegistry = (custom = [],
|
|
19
|
+
var createTaskRegistry = (custom = [], { db = false, email = false } = {}) => {
|
|
15
20
|
const tasks = /* @__PURE__ */ new Map();
|
|
16
|
-
|
|
21
|
+
[
|
|
22
|
+
...builtinTasks,
|
|
23
|
+
...db ? dbTasks : [],
|
|
24
|
+
...email ? emailTasks : []
|
|
25
|
+
].forEach((task) => tasks.set(taskName(task), {
|
|
17
26
|
...task,
|
|
18
27
|
name: taskName(task)
|
|
19
28
|
}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionEntry, ActionLimits, ActionRejectRecord, ActionRunRecord, ActionRunStatus, ActionTriggerType, ConnectorManifest, Environment, InteractionCallbackParam, InteractionNode, InteractionNodeStatus, SpaceRevision, SSRUser } from '@plitzi/sdk-shared';
|
|
1
|
+
import { ActionEmailAdapter, ActionEntry, ActionLimits, ActionRejectRecord, ActionRunRecord, ActionRunStatus, ActionTriggerType, ConnectorManifest, Environment, InteractionCallbackParam, InteractionNode, InteractionNodeStatus, SpaceRevision, SSRUser } from '@plitzi/sdk-shared';
|
|
2
2
|
/** Resolved secret material, as the connector engine already models it. */
|
|
3
3
|
export type ActionCredential = Record<string, string>;
|
|
4
4
|
/**
|
|
@@ -109,6 +109,8 @@ export type ActionTaskContext = {
|
|
|
109
109
|
kv: ActionKvStore;
|
|
110
110
|
/** The database engines this deployment registered, for the `db.query` task. */
|
|
111
111
|
dbDrivers: ActionDbDriver[];
|
|
112
|
+
/** The transport this deployment gave the `email.send` task. Absent → that task is not offered at all. */
|
|
113
|
+
email?: ActionEmailAdapter;
|
|
112
114
|
/** Pushes a `data` frame to a streaming caller. A no-op when nobody negotiated a stream. */
|
|
113
115
|
emit: (chunk: unknown) => void;
|
|
114
116
|
};
|
|
@@ -162,6 +164,8 @@ export type ActionsConfig = {
|
|
|
162
164
|
};
|
|
163
165
|
/** Database engines this deployment lets a flow reach. Empty → the `db.query` task is not offered at all. */
|
|
164
166
|
dbDrivers?: ActionDbDriver[];
|
|
167
|
+
/** How a flow's mail leaves this server. Absent → the `email.send` task is not offered at all. */
|
|
168
|
+
email?: ActionEmailAdapter;
|
|
165
169
|
/**
|
|
166
170
|
* Called once per run that started, for a deployment that keeps a record.
|
|
167
171
|
*
|
|
@@ -197,7 +201,7 @@ export type ActionsConfig = {
|
|
|
197
201
|
};
|
|
198
202
|
/** Re-exported so the module's own files import one place, and a deployment writing an `onRun` or an `onReject`
|
|
199
203
|
* sees the same shapes the module emits. */
|
|
200
|
-
export type { ActionRejectRecord, ActionRunRecord } from '@plitzi/sdk-shared';
|
|
204
|
+
export type { ActionEmailAdapter, ActionEmailMessage, ActionRejectRecord, ActionRunRecord } from '@plitzi/sdk-shared';
|
|
201
205
|
export type ResolvedActionLimits = Required<ActionLimits>;
|
|
202
206
|
export type ActionRunRequest = {
|
|
203
207
|
entry: ActionEntry;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-server",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.4",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"type": "module",
|
|
59
59
|
"sideEffects": false,
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@plitzi/plitzi-sdk": "0.35.
|
|
62
|
-
"@plitzi/sdk-shared": "0.35.
|
|
61
|
+
"@plitzi/plitzi-sdk": "0.35.4",
|
|
62
|
+
"@plitzi/sdk-shared": "0.35.4",
|
|
63
63
|
"ejs": "^6.0.1",
|
|
64
64
|
"esbuild": "^0.28.2",
|
|
65
65
|
"jsonwebtoken": "^9.0.3"
|