n8n-nodes-lifespace 0.1.6 → 0.1.7
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/README.md +19 -1
- package/dist/nodes/LifeSpace/LifeSpace.node.d.ts +7 -0
- package/dist/nodes/LifeSpace/LifeSpace.node.js +285 -30
- package/dist/nodes/LifeSpace/LifeSpace.node.js.map +1 -1
- package/dist/nodes/LifeSpaceTool/LifeSpaceTool.node.d.ts +13 -0
- package/dist/nodes/LifeSpaceTool/LifeSpaceTool.node.js +373 -0
- package/dist/nodes/LifeSpaceTool/LifeSpaceTool.node.js.map +1 -0
- package/dist/nodes/LifeSpaceTool/lifespace.dark.svg +14 -0
- package/dist/nodes/LifeSpaceTool/lifespace.svg +14 -0
- package/dist/nodes/agent/lifeSpaceToolFactory.d.ts +55 -0
- package/dist/nodes/agent/lifeSpaceToolFactory.js +591 -0
- package/dist/nodes/agent/lifeSpaceToolFactory.js.map +1 -0
- package/dist/nodes/lifespaceDiscovery.d.ts +77 -1
- package/dist/nodes/lifespaceDiscovery.js +36 -1
- package/dist/nodes/lifespaceDiscovery.js.map +1 -1
- package/dist/package.json +5 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -74,9 +74,19 @@ Record Type is the LifeSpace `modelKey` (for example `task`). Design-time option
|
|
|
74
74
|
|
|
75
75
|
Calendar-backed models use canonical `capabilityBindings.calendar` field roles instead of Event-specific field names while the node is being configured. Date-only values are normalized to `YYYY-MM-DD`. Create/Update execution does not fetch fresh semantic Discovery solely to produce an adapter-local Calendar conflict error; the canonical mutation goes directly to LifeSpace Core, which remains authoritative for Calendar validation and current authorization.
|
|
76
76
|
|
|
77
|
+
## AI Agent Tool
|
|
78
|
+
|
|
79
|
+
`LifeSpace Tool` is the metadata-driven AI Agent sub-node. Configure one fixed Space, Record Type and semantic operation per Tool instance, then connect multiple instances to the same n8n AI Agent. The Tool name, default description and structured AI input schema are generated from LifeSpace Progressive Runtime Discovery; hand-written descriptions are optional.
|
|
80
|
+
|
|
81
|
+
- Query exposes published search/filter/comparison/local-date-window/sort/pagination semantics. Capability Query mode exposes only the capability-owned parameters and ordering that LifeSpace explicitly publishes; generic-facet composition stays narrowed until LifeSpace #228 defines it.
|
|
82
|
+
- Create/Update schemas come from writable Model fields. Required fields with LifeSpace defaults are optional AI inputs, and fields the AI omits are absent from the outgoing request rather than synthesized as empty/null placeholders.
|
|
83
|
+
- Delete and Action hide optimistic-concurrency `version` from the AI. The Adapter reads the current record version when required and Core remains the final authorization/concurrency/semantic authority.
|
|
84
|
+
- No Task/Event/Wish-specific Tool nodes or field maps are shipped. New models become available through Discovery without source changes.
|
|
85
|
+
- The ordinary `LifeSpace` workflow node remains `usableAsTool` for manually configured `$fromAI(...)` workflows, but `LifeSpace Tool` is the canonical path when the AI should receive the complete dynamic LifeSpace schema.
|
|
86
|
+
|
|
77
87
|
## LifeSpace contract compatibility
|
|
78
88
|
|
|
79
|
-
This package follows the current LifeSpace Core Kernel `0.
|
|
89
|
+
This package follows the current LifeSpace Core Kernel `0.35.0` contract family. It consumes Runtime/Discovery semantics only; Eventing configuration and webhook delivery semantics are owned independently by Integration/Eventing `0.1.0`.
|
|
80
90
|
|
|
81
91
|
The UX depends on these Kernel capabilities:
|
|
82
92
|
|
|
@@ -95,6 +105,9 @@ The UX depends on these Kernel capabilities:
|
|
|
95
105
|
- `0.30.0`: explicit paginated Change History collection and Model Control Plane ownership split;
|
|
96
106
|
- `0.31.0`: Integration/Eventing wire representation moves to the independent Integration/Eventing `0.1.0` contract while Core remains the Runtime authority.
|
|
97
107
|
- `0.32.0`: `modelKey` becomes the sole Runtime address and canonical CRUD/Action paths move under `/models/{modelKey}/records`.
|
|
108
|
+
- `0.33.0`: canonical structural `timeRanges` become available in progressive semantic detail without implying an overlap query API.
|
|
109
|
+
- `0.34.0`: explicit `eq/lt/lte/gt/gte` comparison transports, first-class `createdAt` / `updatedAt` envelope comparisons and Core-owned datetime local-date-window conversion become discoverable.
|
|
110
|
+
- `0.35.0`: grouped `query.capabilityQueries` adds the preferred `calendar.window` viewing-window query with explicit IANA viewing timezone and deterministic mixed all-day/timed ordering.
|
|
98
111
|
|
|
99
112
|
The adapter prefers the `0.27+` progressive flow while configuring a node:
|
|
100
113
|
|
|
@@ -199,9 +212,14 @@ The normal UI supports:
|
|
|
199
212
|
|
|
200
213
|
- optional **Search**;
|
|
201
214
|
- one or more typed **Filters**;
|
|
215
|
+
- Discovery-driven explicit **Number Comparison** and **Date / Time Comparison** rows using the exact LifeSpace-published operator transport;
|
|
216
|
+
- generic **Local Date Windows** for datetime fields advertised by Time Semantics, including envelope `createdAt` / `updatedAt`, with local dates + IANA timezone sent unchanged to Core;
|
|
217
|
+
- optional grouped **Semantic Query** input generated from `query.capabilityQueries` (for example `calendar.window`) plus its published semantic ordering;
|
|
202
218
|
- **Return All** to follow `nextCursor` automatically;
|
|
203
219
|
- **Limit** when Return All is disabled.
|
|
204
220
|
|
|
221
|
+
The adapter never derives explicit comparison parameter names from field naming and never converts local calendar windows to UTC. Those transport names and timezone/DST semantics come from LifeSpace Runtime Semantic Detail. Existing `exact/from/to` filters remain available as compatibility UI and preserve the legacy inclusive `To` behavior.
|
|
222
|
+
|
|
205
223
|
Use **Sorts → Add Sort** to add zero or more sort criteria in priority order. Sortable model fields use authoritative `title` metadata from Runtime Discovery, while envelope fields such as `createdAt` / `updatedAt` are offered only when Discovery advertises them. Multiple criteria are sent as ordered repeated `sort=field:direction` query parameters.
|
|
206
224
|
|
|
207
225
|
Advanced **Options** contain manual **Cursor** as an escape hatch.
|
|
@@ -16,6 +16,12 @@ export declare class LifeSpace implements INodeType {
|
|
|
16
16
|
getBooleanFilterableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
17
17
|
getNumericFilterableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
18
18
|
getTemporalFilterableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
19
|
+
getNumericComparisonFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
20
|
+
getTemporalComparisonFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
21
|
+
getComparisonOperatorsForCurrentField(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
22
|
+
getLocalDateWindowFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
23
|
+
getCapabilityQueries(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
24
|
+
getSemanticSorts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
19
25
|
getPersonFilterableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
20
26
|
getEnumValuesForCurrentFilter(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
21
27
|
getSortableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
@@ -23,6 +29,7 @@ export declare class LifeSpace implements INodeType {
|
|
|
23
29
|
resourceMapping: {
|
|
24
30
|
getRecordFields(this: ILoadOptionsFunctions): Promise<ResourceMapperFields>;
|
|
25
31
|
getActionInputFields(this: ILoadOptionsFunctions): Promise<ResourceMapperFields>;
|
|
32
|
+
getSemanticQueryInputFields(this: ILoadOptionsFunctions): Promise<ResourceMapperFields>;
|
|
26
33
|
};
|
|
27
34
|
};
|
|
28
35
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
@@ -9,6 +9,75 @@ const LEGACY_MODEL_ROUTE_TO_KEY = Object.freeze({
|
|
|
9
9
|
'day-records': 'day_record',
|
|
10
10
|
events: 'event',
|
|
11
11
|
});
|
|
12
|
+
const LOCAL_DATE_WINDOW_SELECTOR_PREFIX = 'lsqw1.';
|
|
13
|
+
const COMPARISON_OPERATOR_LABELS = Object.freeze({
|
|
14
|
+
eq: 'Equals',
|
|
15
|
+
lt: 'Less Than',
|
|
16
|
+
lte: 'Less Than or Equal',
|
|
17
|
+
gt: 'Greater Than',
|
|
18
|
+
gte: 'Greater Than or Equal',
|
|
19
|
+
});
|
|
20
|
+
function comparisonFieldParts(value) {
|
|
21
|
+
const raw = String(value !== null && value !== void 0 ? value : '').trim();
|
|
22
|
+
const separator = raw.indexOf(':');
|
|
23
|
+
if (separator <= 0)
|
|
24
|
+
return { field: raw, valueType: '' };
|
|
25
|
+
return { valueType: raw.slice(0, separator), field: raw.slice(separator + 1) };
|
|
26
|
+
}
|
|
27
|
+
function encodeLocalDateWindowSelector(comparison) {
|
|
28
|
+
const window = comparison.localDateWindow;
|
|
29
|
+
if (!window || comparison.valueType !== 'datetime')
|
|
30
|
+
return '';
|
|
31
|
+
return `${LOCAL_DATE_WINDOW_SELECTOR_PREFIX}${Buffer.from(JSON.stringify([
|
|
32
|
+
comparison.field,
|
|
33
|
+
comparison.valueType,
|
|
34
|
+
window.dateStartParameter,
|
|
35
|
+
window.dateEndExclusiveParameter,
|
|
36
|
+
window.timezoneParameter,
|
|
37
|
+
])).toString('base64url')}`;
|
|
38
|
+
}
|
|
39
|
+
function decodeLocalDateWindowSelector(value) {
|
|
40
|
+
const raw = String(value !== null && value !== void 0 ? value : '').trim();
|
|
41
|
+
if (!raw.startsWith(LOCAL_DATE_WINDOW_SELECTOR_PREFIX))
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const decoded = JSON.parse(Buffer.from(raw.slice(LOCAL_DATE_WINDOW_SELECTOR_PREFIX.length), 'base64url').toString('utf8'));
|
|
45
|
+
if (!Array.isArray(decoded) || decoded.length !== 5 || decoded.some((entry) => typeof entry !== 'string' || !entry))
|
|
46
|
+
return null;
|
|
47
|
+
if (decoded[1] !== 'datetime')
|
|
48
|
+
return null;
|
|
49
|
+
return {
|
|
50
|
+
field: decoded[0],
|
|
51
|
+
valueType: 'datetime',
|
|
52
|
+
dateStartParameter: decoded[2],
|
|
53
|
+
dateEndExclusiveParameter: decoded[3],
|
|
54
|
+
timezoneParameter: decoded[4],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function semanticQueryMapperType(parameter) {
|
|
62
|
+
if (parameter.type === 'boolean')
|
|
63
|
+
return 'boolean';
|
|
64
|
+
if (parameter.type === 'integer' || parameter.type === 'number')
|
|
65
|
+
return 'number';
|
|
66
|
+
return 'string';
|
|
67
|
+
}
|
|
68
|
+
function semanticQueryMapperField(parameter) {
|
|
69
|
+
var _a;
|
|
70
|
+
return {
|
|
71
|
+
id: parameter.parameter,
|
|
72
|
+
displayName: (0, lifespaceDiscovery_1.humanizeKey)(((_a = parameter.role) === null || _a === void 0 ? void 0 : _a.trim()) || parameter.parameter),
|
|
73
|
+
required: parameter.required === true,
|
|
74
|
+
defaultMatch: false,
|
|
75
|
+
canBeUsedToMatch: false,
|
|
76
|
+
display: true,
|
|
77
|
+
type: semanticQueryMapperType(parameter),
|
|
78
|
+
description: `${parameter.type}${parameter.role ? ` · ${parameter.role}` : ''} · LifeSpace query parameter ${parameter.parameter}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
12
81
|
function parseJsonObject(context, itemIndex, value, fieldName) {
|
|
13
82
|
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
14
83
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
@@ -102,14 +171,15 @@ function normalizeActionInput(context, itemIndex, value, fields) {
|
|
|
102
171
|
return result;
|
|
103
172
|
}
|
|
104
173
|
function queryParameters(context, itemIndex, limit, cursorOverride) {
|
|
105
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
174
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
|
|
106
175
|
const qs = {};
|
|
107
176
|
const search = String(context.getNodeParameter('search', itemIndex, '')).trim();
|
|
108
177
|
const options = context.getNodeParameter('options', itemIndex, {});
|
|
109
178
|
const configuredSorts = context.getNodeParameter('sorts.sort', itemIndex, []);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
179
|
+
const semanticSort = String((_a = context.getNodeParameter('semanticSort', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim();
|
|
180
|
+
const legacySortField = String((_b = options.sortField) !== null && _b !== void 0 ? _b : '').trim();
|
|
181
|
+
const legacySortDirection = String((_c = options.sortDirection) !== null && _c !== void 0 ? _c : 'desc').trim();
|
|
182
|
+
const configuredCursor = String((_d = options.cursor) !== null && _d !== void 0 ? _d : '').trim();
|
|
113
183
|
const cursor = cursorOverride !== null && cursorOverride !== void 0 ? cursorOverride : configuredCursor;
|
|
114
184
|
const filters = context.getNodeParameter('filters.filter', itemIndex, []);
|
|
115
185
|
if (search)
|
|
@@ -117,10 +187,10 @@ function queryParameters(context, itemIndex, limit, cursorOverride) {
|
|
|
117
187
|
const orderedSorts = [];
|
|
118
188
|
const usedSortFields = new Set();
|
|
119
189
|
for (const sort of configuredSorts) {
|
|
120
|
-
const field = String((
|
|
190
|
+
const field = String((_e = sort.field) !== null && _e !== void 0 ? _e : '').trim();
|
|
121
191
|
if (!field)
|
|
122
192
|
continue;
|
|
123
|
-
const direction = String((
|
|
193
|
+
const direction = String((_f = sort.direction) !== null && _f !== void 0 ? _f : 'asc').trim();
|
|
124
194
|
if (direction !== 'asc' && direction !== 'desc') {
|
|
125
195
|
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Sort direction for ${field} must be asc or desc`, { itemIndex });
|
|
126
196
|
}
|
|
@@ -130,56 +200,64 @@ function queryParameters(context, itemIndex, limit, cursorOverride) {
|
|
|
130
200
|
usedSortFields.add(field);
|
|
131
201
|
orderedSorts.push(`${field}:${direction}`);
|
|
132
202
|
}
|
|
133
|
-
if (orderedSorts.length
|
|
203
|
+
if (semanticSort && (orderedSorts.length > 0 || legacySortField)) {
|
|
204
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Semantic Sort cannot be combined with field Sorts', { itemIndex });
|
|
205
|
+
}
|
|
206
|
+
if (semanticSort)
|
|
207
|
+
qs.sort = semanticSort;
|
|
208
|
+
if (!semanticSort && orderedSorts.length === 1)
|
|
134
209
|
qs.sort = orderedSorts[0];
|
|
135
|
-
if (orderedSorts.length > 1)
|
|
210
|
+
if (!semanticSort && orderedSorts.length > 1)
|
|
136
211
|
qs.sort = orderedSorts;
|
|
137
|
-
if (orderedSorts.length === 0 && legacySortField)
|
|
212
|
+
if (!semanticSort && orderedSorts.length === 0 && legacySortField)
|
|
138
213
|
qs.sort = `${legacySortField}:${legacySortDirection}`;
|
|
139
214
|
qs.limit = limit;
|
|
140
215
|
if (cursor)
|
|
141
216
|
qs.cursor = cursor;
|
|
142
217
|
const usedKeys = new Set();
|
|
143
|
-
const
|
|
144
|
-
const key = operator === 'from' ? `${field}From` : operator === 'to' ? `${field}To` : field;
|
|
218
|
+
const setQueryParameter = (key, value) => {
|
|
145
219
|
if (usedKeys.has(key)) {
|
|
146
|
-
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Query
|
|
220
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Query parameter ${key} may be supplied only once`, { itemIndex });
|
|
147
221
|
}
|
|
148
222
|
usedKeys.add(key);
|
|
149
223
|
qs[key] = typeof value === 'boolean' ? String(value) : value;
|
|
150
224
|
};
|
|
225
|
+
const setFilter = (field, operator, value) => {
|
|
226
|
+
const key = operator === 'from' ? `${field}From` : operator === 'to' ? `${field}To` : field;
|
|
227
|
+
setQueryParameter(key, value);
|
|
228
|
+
};
|
|
151
229
|
for (const filter of filters) {
|
|
152
|
-
const field = String((
|
|
153
|
-
const value = String((
|
|
154
|
-
const operator = (
|
|
230
|
+
const field = String((_g = filter.field) !== null && _g !== void 0 ? _g : '').trim();
|
|
231
|
+
const value = String((_h = filter.value) !== null && _h !== void 0 ? _h : '');
|
|
232
|
+
const operator = (_j = filter.operator) !== null && _j !== void 0 ? _j : 'exact';
|
|
155
233
|
if (field && value !== '')
|
|
156
234
|
setFilter(field, operator, value);
|
|
157
235
|
}
|
|
158
236
|
const typedFilters = context.getNodeParameter('filters', itemIndex, {});
|
|
159
|
-
for (const row of ((
|
|
160
|
-
const field = String((
|
|
237
|
+
for (const row of ((_k = typedFilters.text) !== null && _k !== void 0 ? _k : [])) {
|
|
238
|
+
const field = String((_l = row.field) !== null && _l !== void 0 ? _l : '').trim();
|
|
161
239
|
if (field && row.value !== undefined && row.value !== '')
|
|
162
240
|
setFilter(field, 'exact', String(row.value));
|
|
163
241
|
}
|
|
164
|
-
for (const row of ((
|
|
165
|
-
const field = String((
|
|
242
|
+
for (const row of ((_m = typedFilters.enum) !== null && _m !== void 0 ? _m : [])) {
|
|
243
|
+
const field = String((_o = row.field) !== null && _o !== void 0 ? _o : '').trim();
|
|
166
244
|
const values = Array.isArray(row.values) ? row.values.map(String).filter(Boolean) : [];
|
|
167
245
|
if (field && values.length)
|
|
168
246
|
setFilter(field, 'exact', values.join(','));
|
|
169
247
|
}
|
|
170
|
-
for (const row of ((
|
|
171
|
-
const field = String((
|
|
248
|
+
for (const row of ((_p = typedFilters.boolean) !== null && _p !== void 0 ? _p : [])) {
|
|
249
|
+
const field = String((_q = row.field) !== null && _q !== void 0 ? _q : '').trim();
|
|
172
250
|
if (field)
|
|
173
251
|
setFilter(field, 'exact', Boolean(row.value));
|
|
174
252
|
}
|
|
175
|
-
for (const row of ((
|
|
176
|
-
const field = String((
|
|
253
|
+
for (const row of ((_r = typedFilters.number) !== null && _r !== void 0 ? _r : [])) {
|
|
254
|
+
const field = String((_s = row.field) !== null && _s !== void 0 ? _s : '').trim();
|
|
177
255
|
const value = Number(row.value);
|
|
178
256
|
if (field && Number.isFinite(value))
|
|
179
|
-
setFilter(field, (
|
|
257
|
+
setFilter(field, (_t = row.operator) !== null && _t !== void 0 ? _t : 'exact', value);
|
|
180
258
|
}
|
|
181
|
-
for (const row of ((
|
|
182
|
-
const encoded = String((
|
|
259
|
+
for (const row of ((_u = typedFilters.temporal) !== null && _u !== void 0 ? _u : [])) {
|
|
260
|
+
const encoded = String((_v = row.field) !== null && _v !== void 0 ? _v : '').trim();
|
|
183
261
|
if (!encoded || row.value === undefined || row.value === '')
|
|
184
262
|
continue;
|
|
185
263
|
const separator = encoded.indexOf(':');
|
|
@@ -188,14 +266,59 @@ function queryParameters(context, itemIndex, limit, cursorOverride) {
|
|
|
188
266
|
const raw = String(row.value);
|
|
189
267
|
const value = fieldType === 'date' ? dateOnlyValue(context, itemIndex, raw, field) : raw;
|
|
190
268
|
if (value !== null)
|
|
191
|
-
setFilter(field, (
|
|
269
|
+
setFilter(field, (_w = row.operator) !== null && _w !== void 0 ? _w : 'exact', value);
|
|
192
270
|
}
|
|
193
|
-
for (const row of ((
|
|
194
|
-
const field = String((
|
|
195
|
-
const target = String((
|
|
271
|
+
for (const row of ((_x = typedFilters.person) !== null && _x !== void 0 ? _x : [])) {
|
|
272
|
+
const field = String((_y = row.field) !== null && _y !== void 0 ? _y : '').trim();
|
|
273
|
+
const target = String((_z = row.target) !== null && _z !== void 0 ? _z : '').trim();
|
|
196
274
|
if (field && target)
|
|
197
275
|
setFilter(field, 'exact', target);
|
|
198
276
|
}
|
|
277
|
+
for (const row of ((_0 = typedFilters.numberComparison) !== null && _0 !== void 0 ? _0 : [])) {
|
|
278
|
+
const field = comparisonFieldParts(row.field).field;
|
|
279
|
+
const parameter = String((_1 = row.parameter) !== null && _1 !== void 0 ? _1 : '').trim();
|
|
280
|
+
const value = Number(row.value);
|
|
281
|
+
if (field && parameter && Number.isFinite(value))
|
|
282
|
+
setQueryParameter(parameter, value);
|
|
283
|
+
}
|
|
284
|
+
for (const row of ((_2 = typedFilters.temporalComparison) !== null && _2 !== void 0 ? _2 : [])) {
|
|
285
|
+
const { field, valueType } = comparisonFieldParts(row.field);
|
|
286
|
+
const parameter = String((_3 = row.parameter) !== null && _3 !== void 0 ? _3 : '').trim();
|
|
287
|
+
if (!field || !parameter || row.value === undefined || row.value === '')
|
|
288
|
+
continue;
|
|
289
|
+
const raw = String(row.value);
|
|
290
|
+
const value = valueType === 'date' ? dateOnlyValue(context, itemIndex, raw, field) : raw;
|
|
291
|
+
if (value !== null)
|
|
292
|
+
setQueryParameter(parameter, value);
|
|
293
|
+
}
|
|
294
|
+
const localDateWindows = context.getNodeParameter('localDateWindows.window', itemIndex, []);
|
|
295
|
+
for (const row of localDateWindows) {
|
|
296
|
+
const selector = decodeLocalDateWindowSelector(row.field);
|
|
297
|
+
if (!selector) {
|
|
298
|
+
if (String((_4 = row.field) !== null && _4 !== void 0 ? _4 : '').trim()) {
|
|
299
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Local Date Window selector is invalid. Re-select the field from Runtime Discovery.', { itemIndex });
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const dateStart = dateOnlyValue(context, itemIndex, row.dateStart, selector.field);
|
|
304
|
+
const dateEndExclusive = dateOnlyValue(context, itemIndex, row.dateEndExclusive, selector.field);
|
|
305
|
+
const timezone = String((_5 = row.timezone) !== null && _5 !== void 0 ? _5 : '').trim();
|
|
306
|
+
if (dateStart !== null)
|
|
307
|
+
setQueryParameter(selector.dateStartParameter, dateStart);
|
|
308
|
+
if (dateEndExclusive !== null)
|
|
309
|
+
setQueryParameter(selector.dateEndExclusiveParameter, dateEndExclusive);
|
|
310
|
+
if (timezone)
|
|
311
|
+
setQueryParameter(selector.timezoneParameter, timezone);
|
|
312
|
+
}
|
|
313
|
+
const semanticQueryInput = mappedValue(context, itemIndex, 'semanticQueryInput');
|
|
314
|
+
for (const [parameter, value] of Object.entries(semanticQueryInput)) {
|
|
315
|
+
if (value === null || value === undefined || value === '')
|
|
316
|
+
continue;
|
|
317
|
+
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
|
|
318
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Semantic Query parameter ${parameter} must be a scalar value`, { itemIndex });
|
|
319
|
+
}
|
|
320
|
+
setQueryParameter(parameter, value);
|
|
321
|
+
}
|
|
199
322
|
return qs;
|
|
200
323
|
}
|
|
201
324
|
function queryPage(context, itemIndex, response) {
|
|
@@ -349,6 +472,24 @@ async function filterFieldOptions(context, types, encodeType = false) {
|
|
|
349
472
|
});
|
|
350
473
|
});
|
|
351
474
|
}
|
|
475
|
+
async function comparisonFieldOptions(context, types) {
|
|
476
|
+
var _a;
|
|
477
|
+
const selected = await optionModel(context);
|
|
478
|
+
if (!selected)
|
|
479
|
+
return [];
|
|
480
|
+
const allowed = new Set(types);
|
|
481
|
+
return ((_a = selected.model.query.comparisons) !== null && _a !== void 0 ? _a : [])
|
|
482
|
+
.filter((comparison) => allowed.has(comparison.valueType))
|
|
483
|
+
.map((comparison) => {
|
|
484
|
+
var _a;
|
|
485
|
+
const field = selected.model.fields.find((entry) => entry.key === comparison.field);
|
|
486
|
+
return {
|
|
487
|
+
name: ((_a = field === null || field === void 0 ? void 0 : field.title) === null || _a === void 0 ? void 0 : _a.trim()) || (0, lifespaceDiscovery_1.humanizeKey)(comparison.field),
|
|
488
|
+
value: `${comparison.valueType}:${comparison.field}`,
|
|
489
|
+
description: `${comparison.source} · ${comparison.valueType} · explicit LifeSpace comparison operators`,
|
|
490
|
+
};
|
|
491
|
+
});
|
|
492
|
+
}
|
|
352
493
|
async function relationFieldOptions(context, cardinality, filterableOnly = false) {
|
|
353
494
|
const selected = await optionModel(context);
|
|
354
495
|
if (!selected)
|
|
@@ -537,6 +678,16 @@ class LifeSpace {
|
|
|
537
678
|
{ displayName: 'Operator', name: 'operator', type: 'options', options: [{ name: 'Equals', value: 'exact' }, { name: 'From / Greater Than or Equal', value: 'from' }, { name: 'To / Less Than or Equal', value: 'to' }], default: 'exact' },
|
|
538
679
|
{ displayName: 'Value', name: 'value', type: 'dateTime', default: '', required: true },
|
|
539
680
|
] },
|
|
681
|
+
{ displayName: 'Number Comparison', name: 'numberComparison', values: [
|
|
682
|
+
{ displayName: 'Field Name or ID', name: 'field', type: 'options', description: 'Fields and envelope values advertised by LifeSpace explicit comparison semantics. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', typeOptions: { loadOptionsMethod: 'getNumericComparisonFields', loadOptionsDependsOn: ['spaceId', 'recordType'] }, options: [], default: '', required: true },
|
|
683
|
+
{ displayName: 'Operator Name or ID', name: 'parameter', type: 'options', description: 'The option value is the exact query parameter published by LifeSpace; the adapter does not derive transport names. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', typeOptions: { loadOptionsMethod: 'getComparisonOperatorsForCurrentField', loadOptionsDependsOn: ['spaceId', 'recordType', '&field'] }, options: [], default: '', required: true },
|
|
684
|
+
{ displayName: 'Value', name: 'value', type: 'number', default: 0, required: true },
|
|
685
|
+
] },
|
|
686
|
+
{ displayName: 'Date / Time Comparison', name: 'temporalComparison', values: [
|
|
687
|
+
{ displayName: 'Field Name or ID', name: 'field', type: 'options', description: 'Includes model date/datetime fields plus LifeSpace envelope timestamps such as createdAt/updatedAt when advertised. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', typeOptions: { loadOptionsMethod: 'getTemporalComparisonFields', loadOptionsDependsOn: ['spaceId', 'recordType'] }, options: [], default: '', required: true },
|
|
688
|
+
{ displayName: 'Operator Name or ID', name: 'parameter', type: 'options', description: 'Uses the exact explicit comparison transport advertised by LifeSpace. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', typeOptions: { loadOptionsMethod: 'getComparisonOperatorsForCurrentField', loadOptionsDependsOn: ['spaceId', 'recordType', '&field'] }, options: [], default: '', required: true },
|
|
689
|
+
{ displayName: 'Value', name: 'value', type: 'dateTime', default: '', required: true },
|
|
690
|
+
] },
|
|
540
691
|
{ displayName: 'Person Filter', name: 'person', values: [
|
|
541
692
|
{ displayName: 'Field Name or ID', name: 'field', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getPersonFilterableFields', loadOptionsDependsOn: ['spaceId', 'recordType'] }, options: [], default: '', required: true },
|
|
542
693
|
{ displayName: 'Person Name or ID', name: 'target', type: 'options', description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getRelationTargetsForCurrentField', loadOptionsDependsOn: ['spaceId', 'recordType', '&field'] }, options: [], default: '', required: true },
|
|
@@ -548,6 +699,45 @@ class LifeSpace {
|
|
|
548
699
|
] },
|
|
549
700
|
],
|
|
550
701
|
},
|
|
702
|
+
{
|
|
703
|
+
displayName: 'Local Date Windows', name: 'localDateWindows', type: 'fixedCollection', default: {},
|
|
704
|
+
placeholder: 'Add Local Date Window', typeOptions: { multipleValues: true },
|
|
705
|
+
displayOptions: { show: { resource: ['modelRecord'], operation: ['list'] } },
|
|
706
|
+
options: [{ displayName: 'Window', name: 'window', values: [
|
|
707
|
+
{ displayName: 'Field Name or ID', name: 'field', type: 'options', typeOptions: { loadOptionsMethod: 'getLocalDateWindowFields', loadOptionsDependsOn: ['spaceId', 'recordType'] }, options: [], default: '', required: true, description: 'Only datetime fields whose published comparison semantics include a local-date-window transport are offered. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' },
|
|
708
|
+
{ displayName: 'Start Date', name: 'dateStart', type: 'dateTime', default: '', required: true, description: 'Inclusive local calendar start date. The adapter submits YYYY-MM-DD and does not calculate UTC boundaries.' },
|
|
709
|
+
{ displayName: 'End Date (Exclusive)', name: 'dateEndExclusive', type: 'dateTime', default: '', required: true, description: 'Exclusive local calendar end date' },
|
|
710
|
+
{ displayName: 'Viewing Timezone', name: 'timezone', type: 'string', default: '', required: true, placeholder: 'Europe/Amsterdam', description: 'IANA timezone passed unchanged to LifeSpace Core' },
|
|
711
|
+
] }],
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
displayName: 'Semantic Query Name or ID', name: 'semanticQueryKey', type: 'options',
|
|
715
|
+
typeOptions: { loadOptionsMethod: 'getCapabilityQueries', loadOptionsDependsOn: ['spaceId', 'recordType'] },
|
|
716
|
+
options: [], default: '',
|
|
717
|
+
displayOptions: { show: { resource: ['modelRecord'], operation: ['list'] } },
|
|
718
|
+
description: 'Optional grouped capability query published by LifeSpace, for example Calendar Window. The adapter does not hard-code Event fields. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
displayName: 'Semantic Query Input', name: 'semanticQueryInput', type: 'resourceMapper',
|
|
722
|
+
default: { mappingMode: 'defineBelow', value: null }, noDataExpression: true,
|
|
723
|
+
typeOptions: {
|
|
724
|
+
loadOptionsDependsOn: ['spaceId', 'recordType', 'semanticQueryKey'],
|
|
725
|
+
resourceMapper: {
|
|
726
|
+
resourceMapperMethod: 'getSemanticQueryInputFields', mode: 'add',
|
|
727
|
+
fieldWords: { singular: 'parameter', plural: 'parameters' }, addAllFields: true, supportAutoMap: false,
|
|
728
|
+
noFieldsError: 'Choose a Semantic Query to load its LifeSpace-published parameters.',
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
displayOptions: { show: { resource: ['modelRecord'], operation: ['list'] } },
|
|
732
|
+
description: 'Parameters are projected directly from query.capabilityQueries. Calendar local dates/timezone are sent to Core unchanged; Core owns overlap and DST semantics.',
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
displayName: 'Semantic Sort Name or ID', name: 'semanticSort', type: 'options',
|
|
736
|
+
typeOptions: { loadOptionsMethod: 'getSemanticSorts', loadOptionsDependsOn: ['spaceId', 'recordType', 'semanticQueryKey'] },
|
|
737
|
+
options: [], default: '',
|
|
738
|
+
displayOptions: { show: { resource: ['modelRecord'], operation: ['list'] } },
|
|
739
|
+
description: 'Optional capability-specific sort published with the selected Semantic Query. Do not combine with field Sorts. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
740
|
+
},
|
|
551
741
|
{
|
|
552
742
|
displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false,
|
|
553
743
|
displayOptions: { show: { resource: ['modelRecord'], operation: ['list'] } },
|
|
@@ -702,6 +892,64 @@ class LifeSpace {
|
|
|
702
892
|
async getBooleanFilterableFields() { return filterFieldOptions(this, ['boolean']); },
|
|
703
893
|
async getNumericFilterableFields() { return filterFieldOptions(this, ['integer', 'number']); },
|
|
704
894
|
async getTemporalFilterableFields() { return filterFieldOptions(this, ['date', 'datetime'], true); },
|
|
895
|
+
async getNumericComparisonFields() { return comparisonFieldOptions(this, ['integer', 'number']); },
|
|
896
|
+
async getTemporalComparisonFields() { return comparisonFieldOptions(this, ['date', 'datetime']); },
|
|
897
|
+
async getComparisonOperatorsForCurrentField() {
|
|
898
|
+
var _a, _b;
|
|
899
|
+
const selected = await optionModel(this);
|
|
900
|
+
const { field } = comparisonFieldParts(this.getCurrentNodeParameter('&field'));
|
|
901
|
+
const comparison = (_a = selected === null || selected === void 0 ? void 0 : selected.model.query.comparisons) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.field === field);
|
|
902
|
+
return ((_b = comparison === null || comparison === void 0 ? void 0 : comparison.operators) !== null && _b !== void 0 ? _b : [])
|
|
903
|
+
.filter((operator) => operator.transport === 'explicit')
|
|
904
|
+
.map((operator) => {
|
|
905
|
+
var _a;
|
|
906
|
+
return ({
|
|
907
|
+
name: (_a = COMPARISON_OPERATOR_LABELS[operator.operator]) !== null && _a !== void 0 ? _a : (0, lifespaceDiscovery_1.humanizeKey)(operator.operator),
|
|
908
|
+
value: operator.parameter,
|
|
909
|
+
description: `${operator.operator} · ${operator.transport} · ${operator.parameter}`,
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
},
|
|
913
|
+
async getLocalDateWindowFields() {
|
|
914
|
+
var _a;
|
|
915
|
+
const selected = await optionModel(this);
|
|
916
|
+
if (!selected)
|
|
917
|
+
return [];
|
|
918
|
+
return ((_a = selected.model.query.comparisons) !== null && _a !== void 0 ? _a : [])
|
|
919
|
+
.filter((comparison) => comparison.valueType === 'datetime' && comparison.localDateWindow)
|
|
920
|
+
.map((comparison) => {
|
|
921
|
+
var _a;
|
|
922
|
+
const field = selected.model.fields.find((entry) => entry.key === comparison.field);
|
|
923
|
+
return {
|
|
924
|
+
name: ((_a = field === null || field === void 0 ? void 0 : field.title) === null || _a === void 0 ? void 0 : _a.trim()) || (0, lifespaceDiscovery_1.humanizeKey)(comparison.field),
|
|
925
|
+
value: encodeLocalDateWindowSelector(comparison),
|
|
926
|
+
description: `${comparison.source} · [start,end) local calendar window · Core resolves timezone/DST`,
|
|
927
|
+
};
|
|
928
|
+
});
|
|
929
|
+
},
|
|
930
|
+
async getCapabilityQueries() {
|
|
931
|
+
var _a;
|
|
932
|
+
const selected = await optionModel(this);
|
|
933
|
+
return ((_a = selected === null || selected === void 0 ? void 0 : selected.model.query.capabilityQueries) !== null && _a !== void 0 ? _a : []).map((query) => ({
|
|
934
|
+
name: `${(0, lifespaceDiscovery_1.humanizeKey)(query.capability)} · ${(0, lifespaceDiscovery_1.humanizeKey)(query.key)}`,
|
|
935
|
+
value: query.key,
|
|
936
|
+
description: `${query.semantics}${query.recurrenceExpansion === false ? ' · recurrence not expanded' : ''}`,
|
|
937
|
+
}));
|
|
938
|
+
},
|
|
939
|
+
async getSemanticSorts() {
|
|
940
|
+
var _a, _b, _c;
|
|
941
|
+
const selected = await optionModel(this);
|
|
942
|
+
const queryKey = (0, lifespaceDiscovery_1.loadOptionParameter)(this, 'semanticQueryKey');
|
|
943
|
+
const query = (_a = selected === null || selected === void 0 ? void 0 : selected.model.query.capabilityQueries) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.key === queryKey);
|
|
944
|
+
return ((_c = (_b = query === null || query === void 0 ? void 0 : query.ordering) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : []).map((value) => {
|
|
945
|
+
var _a, _b;
|
|
946
|
+
return ({
|
|
947
|
+
name: value === ((_a = query === null || query === void 0 ? void 0 : query.ordering) === null || _a === void 0 ? void 0 : _a.default) ? `${value} (Default)` : value,
|
|
948
|
+
value,
|
|
949
|
+
description: `${(_b = query === null || query === void 0 ? void 0 : query.key) !== null && _b !== void 0 ? _b : 'semantic query'} ordering`,
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
},
|
|
705
953
|
async getPersonFilterableFields() { return relationFieldOptions(this, undefined, true); },
|
|
706
954
|
async getEnumValuesForCurrentFilter() {
|
|
707
955
|
var _a, _b;
|
|
@@ -746,6 +994,13 @@ class LifeSpace {
|
|
|
746
994
|
? { fields: action.input.fields.map((field) => mapperField(field, field.required === true)) }
|
|
747
995
|
: { fields: [] };
|
|
748
996
|
},
|
|
997
|
+
async getSemanticQueryInputFields() {
|
|
998
|
+
var _a;
|
|
999
|
+
const selected = await optionModel(this);
|
|
1000
|
+
const queryKey = (0, lifespaceDiscovery_1.loadOptionParameter)(this, 'semanticQueryKey');
|
|
1001
|
+
const query = (_a = selected === null || selected === void 0 ? void 0 : selected.model.query.capabilityQueries) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.key === queryKey);
|
|
1002
|
+
return query ? { fields: query.parameters.map(semanticQueryMapperField) } : { fields: [] };
|
|
1003
|
+
},
|
|
749
1004
|
},
|
|
750
1005
|
};
|
|
751
1006
|
}
|