@softr/sdk 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assertions.js +68 -0
- package/dist/auth/config.js +27 -0
- package/dist/auth/context.d.ts +18 -0
- package/dist/auth/isomorphic.d.ts +73 -0
- package/dist/auth/isomorphic.js +113 -0
- package/dist/auth/methods/password.d.ts +22 -0
- package/dist/auth/methods/password.js +51 -0
- package/dist/auth/methods/signup.d.ts +37 -0
- package/dist/auth/methods/signup.js +89 -0
- package/dist/auth/methods/utils.d.ts +15 -0
- package/dist/auth/methods/utils.js +13 -0
- package/dist/auth/permissions.d.ts +17 -0
- package/dist/auth/permissions.js +13 -0
- package/dist/auth/platform.js +138 -0
- package/dist/auth/server.d.ts +17 -0
- package/dist/auth/server.js +17 -0
- package/dist/auth/session.js +167 -0
- package/dist/{user-D23hwTsf.d.ts → auth/user.d.ts} +1 -1
- package/dist/client/timezone.d.ts +4 -0
- package/dist/client/timezone.js +11 -0
- package/dist/client.d.ts +1 -4
- package/dist/client.js +1 -9
- package/dist/cookies.js +47 -0
- package/dist/core/datasource.d.ts +21 -0
- package/dist/core/datasource.js +196 -0
- package/dist/core/integration.d.ts +7 -0
- package/dist/core/integration.js +32 -0
- package/dist/core/query.d.ts +354 -0
- package/dist/core/query.js +528 -0
- package/dist/core/workflow.d.ts +7 -0
- package/dist/core/workflow.js +19 -0
- package/dist/index.d.ts +8 -685
- package/dist/index.js +4 -1019
- package/dist/routes/discover.d.ts +9 -0
- package/dist/routes/discover.js +198 -0
- package/dist/routes/manifest.d.ts +17 -0
- package/dist/routes/manifest.js +148 -0
- package/dist/routes/mem-fs.js +71 -0
- package/dist/routes/visibility.d.ts +142 -0
- package/dist/routes/visibility.js +125 -0
- package/dist/server.d.ts +5 -84
- package/dist/server.js +5 -194
- package/dist/types.d.ts +105 -0
- package/dist/vite.d.ts +1 -21
- package/dist/vite.js +2 -405
- package/package.json +4 -4
- package/dist/session-GUEI96C8.js +0 -360
package/dist/index.js
CHANGED
|
@@ -1,1020 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { q } from "./core/query.js";
|
|
2
|
+
import { defineVisibility, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError } from "./routes/visibility.js";
|
|
3
|
+
import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser, usePermissions } from "./auth/isomorphic.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const INTERNAL_KIND = Symbol("KIND");
|
|
7
|
-
/**
|
|
8
|
-
* Query builder for filter queries. This exist to alleviate the (LLM?) mental overhead. API filter schema is a bit
|
|
9
|
-
* verbose and confusing, even when dealing with very basic queries. So to fall into the pit of success, aim here
|
|
10
|
-
* is to simplify filtering as much as possible.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* single filter
|
|
14
|
-
* ```tsx
|
|
15
|
-
* const query = q.text("title").contains("hello");
|
|
16
|
-
*
|
|
17
|
-
* const json = buildFilterCriteria(query);
|
|
18
|
-
* // result
|
|
19
|
-
* {
|
|
20
|
-
* "filterCriteria": {
|
|
21
|
-
* "filterConditionGroup": {
|
|
22
|
-
* "logicalOperator": "AND"
|
|
23
|
-
* "filterGroups": [{
|
|
24
|
-
* "logicalOperator": "AND",
|
|
25
|
-
* "filters": [{
|
|
26
|
-
* "subject": "title",
|
|
27
|
-
* "type": "TEXT",
|
|
28
|
-
* "operator": "CONTAINS",
|
|
29
|
-
* "value": "hello"
|
|
30
|
-
* }],
|
|
31
|
-
* }]
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
*
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* combining multiple filters
|
|
40
|
-
* ```tsx
|
|
41
|
-
* const query = q.and(
|
|
42
|
-
* q.text("title").contains("hello"),
|
|
43
|
-
* q.number("views").is(100),
|
|
44
|
-
* q.or(
|
|
45
|
-
* q.text("author").is("John"),
|
|
46
|
-
* q.boolean("isPublished").is(true)
|
|
47
|
-
* )
|
|
48
|
-
* );
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
const q = {
|
|
52
|
-
select(mapping) {
|
|
53
|
-
return mapping;
|
|
54
|
-
},
|
|
55
|
-
and(...filters) {
|
|
56
|
-
return {
|
|
57
|
-
logicalOperator: "AND",
|
|
58
|
-
filters,
|
|
59
|
-
[INTERNAL_KIND]: "LOGICAL_GROUP"
|
|
60
|
-
};
|
|
61
|
-
},
|
|
62
|
-
or(...filters) {
|
|
63
|
-
return {
|
|
64
|
-
logicalOperator: "OR",
|
|
65
|
-
filters,
|
|
66
|
-
[INTERNAL_KIND]: "LOGICAL_GROUP"
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
text(subject) {
|
|
70
|
-
const filter = {
|
|
71
|
-
subject,
|
|
72
|
-
type: "TEXT",
|
|
73
|
-
[INTERNAL_KIND]: "FILTER"
|
|
74
|
-
};
|
|
75
|
-
return {
|
|
76
|
-
is(value) {
|
|
77
|
-
return {
|
|
78
|
-
...filter,
|
|
79
|
-
operator: "IS",
|
|
80
|
-
value
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
isNot(value) {
|
|
84
|
-
return {
|
|
85
|
-
...filter,
|
|
86
|
-
operator: "IS_NOT",
|
|
87
|
-
value
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
contains(value) {
|
|
91
|
-
return {
|
|
92
|
-
...filter,
|
|
93
|
-
operator: "CONTAINS",
|
|
94
|
-
value
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
isOneOf(value) {
|
|
98
|
-
return {
|
|
99
|
-
...filter,
|
|
100
|
-
operator: "IS_ONE_OF",
|
|
101
|
-
value
|
|
102
|
-
};
|
|
103
|
-
},
|
|
104
|
-
isNoneOf(value) {
|
|
105
|
-
return {
|
|
106
|
-
...filter,
|
|
107
|
-
operator: "IS_NONE_OF",
|
|
108
|
-
value
|
|
109
|
-
};
|
|
110
|
-
},
|
|
111
|
-
startsWith(value) {
|
|
112
|
-
return {
|
|
113
|
-
...filter,
|
|
114
|
-
operator: "STARTS_WITH",
|
|
115
|
-
value
|
|
116
|
-
};
|
|
117
|
-
},
|
|
118
|
-
endsWith(value) {
|
|
119
|
-
return {
|
|
120
|
-
...filter,
|
|
121
|
-
operator: "ENDS_WITH",
|
|
122
|
-
value
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
hasAllOf(value) {
|
|
126
|
-
return {
|
|
127
|
-
...filter,
|
|
128
|
-
operator: "HAS_ALL_OF",
|
|
129
|
-
value
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
isEmpty() {
|
|
133
|
-
return {
|
|
134
|
-
...filter,
|
|
135
|
-
operator: "IS_EMPTY"
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
isNotEmpty() {
|
|
139
|
-
return {
|
|
140
|
-
...filter,
|
|
141
|
-
operator: "IS_NOT_EMPTY"
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
},
|
|
146
|
-
boolean(subject) {
|
|
147
|
-
const filter = {
|
|
148
|
-
subject,
|
|
149
|
-
type: "BOOLEAN",
|
|
150
|
-
[INTERNAL_KIND]: "FILTER"
|
|
151
|
-
};
|
|
152
|
-
return {
|
|
153
|
-
is(value) {
|
|
154
|
-
return {
|
|
155
|
-
...filter,
|
|
156
|
-
operator: "IS",
|
|
157
|
-
value
|
|
158
|
-
};
|
|
159
|
-
},
|
|
160
|
-
isNot(value) {
|
|
161
|
-
return {
|
|
162
|
-
...filter,
|
|
163
|
-
operator: "IS_NOT",
|
|
164
|
-
value
|
|
165
|
-
};
|
|
166
|
-
},
|
|
167
|
-
isEmpty() {
|
|
168
|
-
return {
|
|
169
|
-
...filter,
|
|
170
|
-
operator: "IS_EMPTY"
|
|
171
|
-
};
|
|
172
|
-
},
|
|
173
|
-
isNotEmpty() {
|
|
174
|
-
return {
|
|
175
|
-
...filter,
|
|
176
|
-
operator: "IS_NOT_EMPTY"
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
},
|
|
181
|
-
number(subject) {
|
|
182
|
-
const filter = {
|
|
183
|
-
subject,
|
|
184
|
-
type: "NUMERIC",
|
|
185
|
-
[INTERNAL_KIND]: "FILTER"
|
|
186
|
-
};
|
|
187
|
-
return {
|
|
188
|
-
is(value) {
|
|
189
|
-
return {
|
|
190
|
-
...filter,
|
|
191
|
-
operator: "IS",
|
|
192
|
-
value: value.toString()
|
|
193
|
-
};
|
|
194
|
-
},
|
|
195
|
-
isNot(value) {
|
|
196
|
-
return {
|
|
197
|
-
...filter,
|
|
198
|
-
operator: "IS_NOT",
|
|
199
|
-
value: value.toString()
|
|
200
|
-
};
|
|
201
|
-
},
|
|
202
|
-
gt(value) {
|
|
203
|
-
return {
|
|
204
|
-
...filter,
|
|
205
|
-
operator: "GT",
|
|
206
|
-
value: value.toString()
|
|
207
|
-
};
|
|
208
|
-
},
|
|
209
|
-
gte(value) {
|
|
210
|
-
return {
|
|
211
|
-
...filter,
|
|
212
|
-
operator: "GTE",
|
|
213
|
-
value: value.toString()
|
|
214
|
-
};
|
|
215
|
-
},
|
|
216
|
-
lt(value) {
|
|
217
|
-
return {
|
|
218
|
-
...filter,
|
|
219
|
-
operator: "LT",
|
|
220
|
-
value: value.toString()
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
lte(value) {
|
|
224
|
-
return {
|
|
225
|
-
...filter,
|
|
226
|
-
operator: "LTE",
|
|
227
|
-
value: value.toString()
|
|
228
|
-
};
|
|
229
|
-
},
|
|
230
|
-
between(start, end) {
|
|
231
|
-
return {
|
|
232
|
-
...filter,
|
|
233
|
-
operator: "BETWEEN",
|
|
234
|
-
value: [start.toString(), end.toString()]
|
|
235
|
-
};
|
|
236
|
-
},
|
|
237
|
-
isEmpty() {
|
|
238
|
-
return {
|
|
239
|
-
...filter,
|
|
240
|
-
operator: "IS_EMPTY"
|
|
241
|
-
};
|
|
242
|
-
},
|
|
243
|
-
isNotEmpty() {
|
|
244
|
-
return {
|
|
245
|
-
...filter,
|
|
246
|
-
operator: "IS_NOT_EMPTY"
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
},
|
|
251
|
-
array(subject) {
|
|
252
|
-
const filter = {
|
|
253
|
-
subject,
|
|
254
|
-
type: "ARRAY",
|
|
255
|
-
[INTERNAL_KIND]: "FILTER"
|
|
256
|
-
};
|
|
257
|
-
return {
|
|
258
|
-
is(value) {
|
|
259
|
-
return {
|
|
260
|
-
...filter,
|
|
261
|
-
operator: "IS",
|
|
262
|
-
value
|
|
263
|
-
};
|
|
264
|
-
},
|
|
265
|
-
isOneOf(value) {
|
|
266
|
-
return {
|
|
267
|
-
...filter,
|
|
268
|
-
operator: "IS_ONE_OF",
|
|
269
|
-
value
|
|
270
|
-
};
|
|
271
|
-
},
|
|
272
|
-
isNoneOf(value) {
|
|
273
|
-
return {
|
|
274
|
-
...filter,
|
|
275
|
-
operator: "IS_NONE_OF",
|
|
276
|
-
value
|
|
277
|
-
};
|
|
278
|
-
},
|
|
279
|
-
hasAllOf(value) {
|
|
280
|
-
return {
|
|
281
|
-
...filter,
|
|
282
|
-
operator: "HAS_ALL_OF",
|
|
283
|
-
value
|
|
284
|
-
};
|
|
285
|
-
},
|
|
286
|
-
isEmpty() {
|
|
287
|
-
return {
|
|
288
|
-
...filter,
|
|
289
|
-
operator: "IS_EMPTY"
|
|
290
|
-
};
|
|
291
|
-
},
|
|
292
|
-
isNotEmpty() {
|
|
293
|
-
return {
|
|
294
|
-
...filter,
|
|
295
|
-
operator: "IS_NOT_EMPTY"
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
},
|
|
300
|
-
date(subject) {
|
|
301
|
-
const filter = {
|
|
302
|
-
subject,
|
|
303
|
-
type: "DATE",
|
|
304
|
-
[INTERNAL_KIND]: "FILTER"
|
|
305
|
-
};
|
|
306
|
-
return {
|
|
307
|
-
is(value) {
|
|
308
|
-
return {
|
|
309
|
-
...filter,
|
|
310
|
-
operator: "IS",
|
|
311
|
-
value
|
|
312
|
-
};
|
|
313
|
-
},
|
|
314
|
-
isNot(value) {
|
|
315
|
-
return {
|
|
316
|
-
...filter,
|
|
317
|
-
operator: "IS_NOT",
|
|
318
|
-
value
|
|
319
|
-
};
|
|
320
|
-
},
|
|
321
|
-
gt(value) {
|
|
322
|
-
return {
|
|
323
|
-
...filter,
|
|
324
|
-
operator: "GT",
|
|
325
|
-
value
|
|
326
|
-
};
|
|
327
|
-
},
|
|
328
|
-
gte(value) {
|
|
329
|
-
return {
|
|
330
|
-
...filter,
|
|
331
|
-
operator: "GTE",
|
|
332
|
-
value
|
|
333
|
-
};
|
|
334
|
-
},
|
|
335
|
-
lt(value) {
|
|
336
|
-
return {
|
|
337
|
-
...filter,
|
|
338
|
-
operator: "LT",
|
|
339
|
-
value
|
|
340
|
-
};
|
|
341
|
-
},
|
|
342
|
-
lte(value) {
|
|
343
|
-
return {
|
|
344
|
-
...filter,
|
|
345
|
-
operator: "LTE",
|
|
346
|
-
value
|
|
347
|
-
};
|
|
348
|
-
},
|
|
349
|
-
between(start, end) {
|
|
350
|
-
return {
|
|
351
|
-
...filter,
|
|
352
|
-
operator: "BETWEEN",
|
|
353
|
-
value: [start, end]
|
|
354
|
-
};
|
|
355
|
-
},
|
|
356
|
-
isNotBetween(start, end) {
|
|
357
|
-
return {
|
|
358
|
-
...filter,
|
|
359
|
-
operator: "IS_NOT_BETWEEN",
|
|
360
|
-
value: [start, end]
|
|
361
|
-
};
|
|
362
|
-
},
|
|
363
|
-
isEmpty() {
|
|
364
|
-
return {
|
|
365
|
-
...filter,
|
|
366
|
-
operator: "IS_EMPTY"
|
|
367
|
-
};
|
|
368
|
-
},
|
|
369
|
-
isNotEmpty() {
|
|
370
|
-
return {
|
|
371
|
-
...filter,
|
|
372
|
-
operator: "IS_NOT_EMPTY"
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
},
|
|
377
|
-
asc(subject) {
|
|
378
|
-
return {
|
|
379
|
-
sortingField: subject,
|
|
380
|
-
sortType: "ASC",
|
|
381
|
-
[INTERNAL_KIND]: "ORDER_BY"
|
|
382
|
-
};
|
|
383
|
-
},
|
|
384
|
-
desc(subject) {
|
|
385
|
-
return {
|
|
386
|
-
sortingField: subject,
|
|
387
|
-
sortType: "DESC",
|
|
388
|
-
[INTERNAL_KIND]: "ORDER_BY"
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
/**
|
|
393
|
-
* Converts filter utility conditions to a format our backend supports
|
|
394
|
-
*
|
|
395
|
-
* @example
|
|
396
|
-
* ```tsx
|
|
397
|
-
* const query = q.text("title").contains("hello");
|
|
398
|
-
*
|
|
399
|
-
* const json = buildFilterCriteria(query);
|
|
400
|
-
* // result
|
|
401
|
-
* {
|
|
402
|
-
* "filterCriteria": {
|
|
403
|
-
* "filterConditionGroup": {
|
|
404
|
-
* "logicalOperator": "AND"
|
|
405
|
-
* "filterGroups": [{
|
|
406
|
-
* "logicalOperator": "AND",
|
|
407
|
-
* "filters": [{
|
|
408
|
-
* "subject": "title",
|
|
409
|
-
* "type": "TEXT",
|
|
410
|
-
* "operator": "CONTAINS",
|
|
411
|
-
* "value": "hello"
|
|
412
|
-
* }],
|
|
413
|
-
* }]
|
|
414
|
-
* }
|
|
415
|
-
* }
|
|
416
|
-
* }
|
|
417
|
-
*
|
|
418
|
-
* ```
|
|
419
|
-
*/
|
|
420
|
-
function buildFilterCriteria(input) {
|
|
421
|
-
if (!(INTERNAL_KIND in input)) throw new Error("Filters cannot be constructed manually. Use the 'q' helper.");
|
|
422
|
-
if (isFilter(input)) return { filterConditionGroup: {
|
|
423
|
-
logicalOperator: "AND",
|
|
424
|
-
filterGroups: [{
|
|
425
|
-
logicalOperator: "AND",
|
|
426
|
-
filters: [stripInternalKind(input)]
|
|
427
|
-
}]
|
|
428
|
-
} };
|
|
429
|
-
if (isFilterGroup(input)) {
|
|
430
|
-
if (input.filters.length === 0) return { filterConditionGroup: {
|
|
431
|
-
logicalOperator: input.logicalOperator,
|
|
432
|
-
filterGroups: []
|
|
433
|
-
} };
|
|
434
|
-
return { filterConditionGroup: {
|
|
435
|
-
logicalOperator: input.logicalOperator,
|
|
436
|
-
filterGroups: input.filters.map((inner) => {
|
|
437
|
-
if (isFilter(inner)) return {
|
|
438
|
-
logicalOperator: "AND",
|
|
439
|
-
filters: [stripInternalKind(inner)]
|
|
440
|
-
};
|
|
441
|
-
if (!inner.filters.every(isFilter)) throw new Error("Cannot nest filter groups more than three levels deep.");
|
|
442
|
-
return {
|
|
443
|
-
logicalOperator: inner.logicalOperator,
|
|
444
|
-
filters: inner.filters.map(stripInternalKind)
|
|
445
|
-
};
|
|
446
|
-
})
|
|
447
|
-
} };
|
|
448
|
-
}
|
|
449
|
-
throw new Error("Unknown filter kind: " + input[INTERNAL_KIND]);
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Converts order-by utility conditions to a format our backend supports
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* single orderBy
|
|
456
|
-
* ```tsx
|
|
457
|
-
* const orderBy = buildOrderBy(q.asc("title"));
|
|
458
|
-
* // result: [{ sortingField: "title", sortType: "ASC" }]
|
|
459
|
-
* ```
|
|
460
|
-
*
|
|
461
|
-
* @example
|
|
462
|
-
* multiple orderBys
|
|
463
|
-
* ```tsx
|
|
464
|
-
* const orderBy = buildOrderBy([q.desc("createdAt"), q.asc("title")]);
|
|
465
|
-
* // result: [
|
|
466
|
-
* // { sortingField: "createdAt", sortType: "DESC" },
|
|
467
|
-
* // { sortingField: "title", sortType: "ASC" }
|
|
468
|
-
* // ]
|
|
469
|
-
* ```
|
|
470
|
-
*/
|
|
471
|
-
function buildOrderBy(input) {
|
|
472
|
-
const orderBys = Array.isArray(input) ? input : [input];
|
|
473
|
-
if (!orderBys.every((ob) => ob[INTERNAL_KIND] === "ORDER_BY")) throw new Error("orderBy conditions cannot be constructed manually. Use the 'q' helper.");
|
|
474
|
-
return orderBys.map((orderBy) => ({
|
|
475
|
-
sortingField: orderBy.sortingField,
|
|
476
|
-
sortType: orderBy.sortType
|
|
477
|
-
}));
|
|
478
|
-
}
|
|
479
|
-
function dealiasOrderBy(orderbys, aliases) {
|
|
480
|
-
return orderbys.map((orderBy) => {
|
|
481
|
-
const subject = aliases[orderBy.sortingField];
|
|
482
|
-
if (!subject) throw new Error(`Could not find an alias for subject "${orderBy.sortingField}"`);
|
|
483
|
-
return {
|
|
484
|
-
...orderBy,
|
|
485
|
-
sortingField: subject
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Replaces filter subjects with their aliased values.
|
|
491
|
-
*
|
|
492
|
-
* Throws an error if any filter subject does not have a corresponding alias in the provided mapping.
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```tsx
|
|
496
|
-
* const criteria = buildFilterCriteria(q.text("name").contains("John"));
|
|
497
|
-
* const aliases = { name: "abcxy123" };
|
|
498
|
-
* const dealiased = dealiasFilterCriteria(criteria, aliases);
|
|
499
|
-
* // Result: filter subject is now "abcxyz123" instead of "name"
|
|
500
|
-
* ```
|
|
501
|
-
*/
|
|
502
|
-
function dealiasFilterCriteria(criteria, aliases) {
|
|
503
|
-
if (!criteria.filterConditionGroup) return criteria;
|
|
504
|
-
return { filterConditionGroup: {
|
|
505
|
-
...criteria.filterConditionGroup,
|
|
506
|
-
filterGroups: criteria.filterConditionGroup.filterGroups.map((group) => ({
|
|
507
|
-
...group,
|
|
508
|
-
filters: group.filters.map(dealiasFilter)
|
|
509
|
-
}))
|
|
510
|
-
} };
|
|
511
|
-
function dealiasFilter(filter) {
|
|
512
|
-
const subject = aliases[filter.subject];
|
|
513
|
-
if (!subject) throw new Error(`Could not find an alias for subject "${subject}"`);
|
|
514
|
-
return {
|
|
515
|
-
...filter,
|
|
516
|
-
subject
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function stripInternalKind(item) {
|
|
521
|
-
const { [INTERNAL_KIND]: _,...rest } = item;
|
|
522
|
-
return rest;
|
|
523
|
-
}
|
|
524
|
-
function isFilterGroup(item) {
|
|
525
|
-
return item[INTERNAL_KIND] === "LOGICAL_GROUP";
|
|
526
|
-
}
|
|
527
|
-
function isFilter(item) {
|
|
528
|
-
return item[INTERNAL_KIND] === "FILTER";
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
//#endregion
|
|
532
|
-
//#region src/routes/visibility.ts
|
|
533
|
-
/**
|
|
534
|
-
* Why a visitor was denied.
|
|
535
|
-
*
|
|
536
|
-
* - `unauthenticated` — the route needs a signed-in visitor and there is none.
|
|
537
|
-
* - `forbidden` — signed in, but in none of the groups the route names.
|
|
538
|
-
* - `alreadySignedIn` — the route is for signed-out visitors only.
|
|
539
|
-
*/
|
|
540
|
-
const ACCESS_REASONS = [
|
|
541
|
-
"unauthenticated",
|
|
542
|
-
"forbidden",
|
|
543
|
-
"alreadySignedIn"
|
|
544
|
-
];
|
|
545
|
-
/** Decides whether a visitor passes a visibility rule, without throwing. */
|
|
546
|
-
function checkAccess(rule, context) {
|
|
547
|
-
const { user } = context;
|
|
548
|
-
switch (rule.userGroup) {
|
|
549
|
-
case "ALL_USERS": return { ok: true };
|
|
550
|
-
case "NON_LOGGED_IN_USERS": return user === null ? { ok: true } : {
|
|
551
|
-
ok: false,
|
|
552
|
-
reason: "alreadySignedIn"
|
|
553
|
-
};
|
|
554
|
-
case "LOGGED_IN_USERS": {
|
|
555
|
-
if (user === null) return {
|
|
556
|
-
ok: false,
|
|
557
|
-
reason: "unauthenticated"
|
|
558
|
-
};
|
|
559
|
-
const required = rule.userGroupIds ?? [];
|
|
560
|
-
if (required.length === 0) return { ok: true };
|
|
561
|
-
return required.some((id) => user.userGroupIds.includes(id)) ? { ok: true } : {
|
|
562
|
-
ok: false,
|
|
563
|
-
reason: "forbidden"
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function isAccessReason(value) {
|
|
569
|
-
return ACCESS_REASONS.some((reason) => reason === value);
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* True for any access error thrown by a guard, whatever its reason.
|
|
573
|
-
*
|
|
574
|
-
* An access error arrives as `unknown` — hydrated from the page, or thrown by
|
|
575
|
-
* anything else at all — so every field is checked before it is narrowed.
|
|
576
|
-
*/
|
|
577
|
-
function isAccessError(error) {
|
|
578
|
-
if (typeof error !== "object" || error === null) return false;
|
|
579
|
-
const candidate = error;
|
|
580
|
-
if (candidate.type !== "access_error") return false;
|
|
581
|
-
return isAccessReason(candidate.reason);
|
|
582
|
-
}
|
|
583
|
-
function isReason(error, reason) {
|
|
584
|
-
return isAccessError(error) && error.reason === reason;
|
|
585
|
-
}
|
|
586
|
-
/** True for an access error thrown because nobody was signed in. */
|
|
587
|
-
function isUnauthenticatedError(error) {
|
|
588
|
-
return isReason(error, "unauthenticated");
|
|
589
|
-
}
|
|
590
|
-
/** True for an access error thrown because the visitor was in none of the groups. */
|
|
591
|
-
function isForbiddenError(error) {
|
|
592
|
-
return isReason(error, "forbidden");
|
|
593
|
-
}
|
|
594
|
-
/** True for an access error thrown because a signed-out-only route saw a user. */
|
|
595
|
-
function isAlreadySignedInError(error) {
|
|
596
|
-
return isReason(error, "alreadySignedIn");
|
|
597
|
-
}
|
|
598
|
-
const DEFAULT_ACCESS_BEHAVIOUR = {
|
|
599
|
-
unauthenticated: {
|
|
600
|
-
strategy: "redirect",
|
|
601
|
-
to: "/login"
|
|
602
|
-
},
|
|
603
|
-
forbidden: { strategy: "throw" },
|
|
604
|
-
alreadySignedIn: {
|
|
605
|
-
strategy: "redirect",
|
|
606
|
-
to: "/"
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
/** What a guard throws for a reason: a redirect, or an {@link AccessError}. */
|
|
610
|
-
function thrownFor(reason, overrides) {
|
|
611
|
-
const behaviour = overrides?.[reason] ?? DEFAULT_ACCESS_BEHAVIOUR[reason];
|
|
612
|
-
const resolved = typeof behaviour === "string" ? {
|
|
613
|
-
strategy: "redirect",
|
|
614
|
-
to: behaviour
|
|
615
|
-
} : behaviour;
|
|
616
|
-
if (resolved.strategy === "redirect") return redirect({ to: resolved.to });
|
|
617
|
-
return {
|
|
618
|
-
type: "access_error",
|
|
619
|
-
reason
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Declares who may see a route, and returns the handle that enforces it.
|
|
624
|
-
*
|
|
625
|
-
* One call per route file, at module scope: the build reads these declarations
|
|
626
|
-
* to publish the app's route visibility, so the rule must be a literal.
|
|
627
|
-
*
|
|
628
|
-
* @example
|
|
629
|
-
* const visibility = defineVisibility({ userGroup: "LOGGED_IN_USERS" })
|
|
630
|
-
*
|
|
631
|
-
* export const Route = createFileRoute("/about")({
|
|
632
|
-
* beforeLoad: visibility.guard(),
|
|
633
|
-
* component: About,
|
|
634
|
-
* })
|
|
635
|
-
*/
|
|
636
|
-
function defineVisibility(rule) {
|
|
637
|
-
return {
|
|
638
|
-
guard: (overrides) => ({ context }) => {
|
|
639
|
-
const result = checkAccess(rule, context);
|
|
640
|
-
if (result.ok) return;
|
|
641
|
-
throw thrownFor(result.reason, overrides);
|
|
642
|
-
},
|
|
643
|
-
assert: (context) => {
|
|
644
|
-
const result = checkAccess(rule, context);
|
|
645
|
-
if (result.ok) return;
|
|
646
|
-
throw thrownFor(result.reason, void 0);
|
|
647
|
-
},
|
|
648
|
-
isAuthorized: (context) => checkAccess(rule, context).ok,
|
|
649
|
-
check: (context) => checkAccess(rule, context)
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
//#endregion
|
|
654
|
-
//#region src/auth/isomorphic.ts
|
|
655
|
-
/**
|
|
656
|
-
* Resolves the Softr visitor for every request, so that `getSoftrUser` and the
|
|
657
|
-
* route guards have someone to check.
|
|
658
|
-
*
|
|
659
|
-
* Register it once, as a global request middleware in the app's `src/start.ts`.
|
|
660
|
-
* It touches nothing but the visitor.
|
|
661
|
-
*
|
|
662
|
-
* @example
|
|
663
|
-
* export const startInstance = createStart(() => ({
|
|
664
|
-
* requestMiddleware: [softrAuthMiddleware()],
|
|
665
|
-
* }))
|
|
666
|
-
*/
|
|
667
|
-
function softrAuthMiddleware() {
|
|
668
|
-
return createMiddleware({ type: "request" }).server(async ({ next }) => {
|
|
669
|
-
async function getUser() {
|
|
670
|
-
const token = readSessionToken();
|
|
671
|
-
if (token === null) return null;
|
|
672
|
-
const cached = readSessionData(token);
|
|
673
|
-
if (cached !== null) return cached;
|
|
674
|
-
const user = await fetchVisitor(token);
|
|
675
|
-
if (user === null) {
|
|
676
|
-
clearSession();
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
|
-
writeCookies([makeSessionDataCookie(user, token)]);
|
|
680
|
-
return user;
|
|
681
|
-
}
|
|
682
|
-
return next({ context: { user: await getUser() } });
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Returns the signed-in visitor, or `null` when nobody is signed in.
|
|
687
|
-
*
|
|
688
|
-
* Await it in the root route's `beforeLoad` to publish the visitor as route
|
|
689
|
-
* context, which every `defineVisibility` guard then reads. It requires
|
|
690
|
-
* {@link softrAuthMiddleware} to be registered.
|
|
691
|
-
*
|
|
692
|
-
* @example
|
|
693
|
-
* export const Route = createRootRouteWithContext<SoftrContext>()({
|
|
694
|
-
* beforeLoad: async () => ({ user: await getSoftrUser() }),
|
|
695
|
-
* })
|
|
696
|
-
*/
|
|
697
|
-
const getSoftrUser = createServerFn({ method: "GET" }).handler(async () => {
|
|
698
|
-
const context = getGlobalStartContext();
|
|
699
|
-
assert(typeof context === "object" && context !== null && "user" in context, "softrAuthMiddleware has not run: the Start global context carries no `user`. Register it as a global request middleware, e.g. createStart(() => ({ requestMiddleware: [softrAuthMiddleware()] })).");
|
|
700
|
-
return context.user ?? null;
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
//#endregion
|
|
704
|
-
//#region src/core/datasource.ts
|
|
705
|
-
const MAX_RECORD_COUNT = 100;
|
|
706
|
-
const COUNT_CNT = "COUNT_CNT";
|
|
707
|
-
var DatasourceAPIImpl = class {
|
|
708
|
-
id;
|
|
709
|
-
fetch;
|
|
710
|
-
userTimezone;
|
|
711
|
-
constructor(id, fetch$1, userTimezone = "UTC") {
|
|
712
|
-
this.id = id;
|
|
713
|
-
this.fetch = fetch$1;
|
|
714
|
-
this.userTimezone = userTimezone;
|
|
715
|
-
}
|
|
716
|
-
async list(options = {}) {
|
|
717
|
-
const { select, where, orderBy } = options;
|
|
718
|
-
const count = Math.min(options.limit ?? 20, MAX_RECORD_COUNT);
|
|
719
|
-
const offset = options.offset ?? null;
|
|
720
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
721
|
-
const sortingOption = orderBy ? dealiasOrderBy(buildOrderBy(orderBy), select ?? {}) : void 0;
|
|
722
|
-
const res = await this.fetch(`/api/datasources/${this.id}/records`, {
|
|
723
|
-
method: "POST",
|
|
724
|
-
headers: {
|
|
725
|
-
"Content-Type": "application/json",
|
|
726
|
-
"X-User-Timezone": options.timeZone ?? this.userTimezone
|
|
727
|
-
},
|
|
728
|
-
body: JSON.stringify({
|
|
729
|
-
options: { userLocale: "en-US" },
|
|
730
|
-
filterCriteria,
|
|
731
|
-
sortingOption,
|
|
732
|
-
pagingOption: {
|
|
733
|
-
offset,
|
|
734
|
-
count
|
|
735
|
-
}
|
|
736
|
-
})
|
|
737
|
-
});
|
|
738
|
-
if (!res.ok) throw new Error(`Softr datasource list failed for "${this.id}": ${res.status}`);
|
|
739
|
-
const payload = await res.json();
|
|
740
|
-
const nextOffset = payload.offset < payload.total ? payload.offset : null;
|
|
741
|
-
return {
|
|
742
|
-
records: payload.items.map((item) => ({
|
|
743
|
-
id: item.id,
|
|
744
|
-
fields: select ? aliasFields(item.fields, select) : item.fields
|
|
745
|
-
})),
|
|
746
|
-
total: payload.total,
|
|
747
|
-
nextOffset
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
async get(options) {
|
|
751
|
-
if (options.id !== void 0) {
|
|
752
|
-
if (!options.id) throw new Error(`Softr datasource get: id must not be empty`);
|
|
753
|
-
const res = await this.fetch(`/api/datasources/${this.id}/records/${options.id}`, {
|
|
754
|
-
method: "POST",
|
|
755
|
-
headers: {
|
|
756
|
-
"Content-Type": "application/json",
|
|
757
|
-
"X-User-Timezone": options.timeZone ?? this.userTimezone
|
|
758
|
-
},
|
|
759
|
-
body: JSON.stringify({ options: { userLocale: "en-US" } })
|
|
760
|
-
});
|
|
761
|
-
if (!res.ok) throw new Error(`Softr datasource get failed for "${this.id}": ${res.status}`);
|
|
762
|
-
const contentLength = res.headers.get("Content-Length");
|
|
763
|
-
if (contentLength && parseInt(contentLength, 10) === 0) return null;
|
|
764
|
-
return res.json();
|
|
765
|
-
}
|
|
766
|
-
return (await this.list({
|
|
767
|
-
where: options.where,
|
|
768
|
-
limit: 1,
|
|
769
|
-
timeZone: options.timeZone
|
|
770
|
-
})).records[0] ?? null;
|
|
771
|
-
}
|
|
772
|
-
async create(options) {
|
|
773
|
-
const res = await this.fetch(`/api/datasources/${this.id}/records`, {
|
|
774
|
-
method: "POST",
|
|
775
|
-
headers: { "Content-Type": "application/json" },
|
|
776
|
-
body: JSON.stringify({ fields: options.fields })
|
|
777
|
-
});
|
|
778
|
-
if (!res.ok) throw new Error(`Softr datasource create failed for "${this.id}": ${res.status}`);
|
|
779
|
-
return (await res.json()).record;
|
|
780
|
-
}
|
|
781
|
-
async update(options) {
|
|
782
|
-
if (!options.id) throw new Error(`Softr datasource update: id must not be empty`);
|
|
783
|
-
const res = await this.fetch(`/api/datasources/${this.id}/records/${options.id}`, {
|
|
784
|
-
method: "PATCH",
|
|
785
|
-
headers: { "Content-Type": "application/json" },
|
|
786
|
-
body: JSON.stringify({ fields: options.fields })
|
|
787
|
-
});
|
|
788
|
-
if (!res.ok) throw new Error(`Softr datasource update failed for "${this.id}": ${res.status}`);
|
|
789
|
-
return (await res.json()).record;
|
|
790
|
-
}
|
|
791
|
-
async delete(id) {
|
|
792
|
-
const res = await this.fetch(`/api/datasources/${this.id}/records/${id}`, { method: "DELETE" });
|
|
793
|
-
if (!res.ok) throw new Error(`Softr datasource delete failed for "${this.id}": ${res.status}`);
|
|
794
|
-
}
|
|
795
|
-
async metric(options) {
|
|
796
|
-
const { select, where, aggregation, field } = options;
|
|
797
|
-
const fieldId = field ? select?.[field] ?? field : void 0;
|
|
798
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
799
|
-
const res = await this.fetch(`/api/datasources/${this.id}/chartMetricData`, {
|
|
800
|
-
method: "POST",
|
|
801
|
-
headers: {
|
|
802
|
-
"Content-Type": "application/json",
|
|
803
|
-
"X-User-Timezone": options.timeZone ?? this.userTimezone
|
|
804
|
-
},
|
|
805
|
-
body: JSON.stringify({
|
|
806
|
-
options: { userLocale: "en-US" },
|
|
807
|
-
calculationField: {
|
|
808
|
-
field: fieldId,
|
|
809
|
-
aggregation
|
|
810
|
-
},
|
|
811
|
-
filterCriteria
|
|
812
|
-
})
|
|
813
|
-
});
|
|
814
|
-
if (!res.ok) throw new Error(`Softr datasource metric failed for "${this.id}": ${res.status}`);
|
|
815
|
-
const payload = await res.json();
|
|
816
|
-
const executionTimestamp = /* @__PURE__ */ new Date(payload.executionTimestamp * 1e3);
|
|
817
|
-
let value;
|
|
818
|
-
if (aggregation === "COUNT") value = payload.data.find((item) => COUNT_CNT in item)?.[COUNT_CNT] ?? null;
|
|
819
|
-
else {
|
|
820
|
-
if (!fieldId) throw new Error(`field is required for aggregation "${aggregation}"`);
|
|
821
|
-
value = payload.data.find((item) => fieldId in item)?.[fieldId] ?? null;
|
|
822
|
-
}
|
|
823
|
-
return {
|
|
824
|
-
value,
|
|
825
|
-
executionTimestamp
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
async chartData(options) {
|
|
829
|
-
const { select, where, groupBy, metrics, orderBy } = options;
|
|
830
|
-
const filterCriteria = where ? dealiasFilterCriteria(buildFilterCriteria(where), select ?? {}) : {};
|
|
831
|
-
const sortingOption = orderBy ? dealiasOrderBy(buildOrderBy(orderBy), select ?? {})[0] : void 0;
|
|
832
|
-
const groupByFields = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((field) => ({ field: select?.[field] ?? field }));
|
|
833
|
-
const yAxisFields = Object.values(metrics).map(({ field, aggregation }) => ({
|
|
834
|
-
field: field ? select?.[field] ?? field : void 0,
|
|
835
|
-
aggregation
|
|
836
|
-
}));
|
|
837
|
-
const res = await this.fetch(`/api/datasources/${this.id}/chartData`, {
|
|
838
|
-
method: "POST",
|
|
839
|
-
headers: {
|
|
840
|
-
"Content-Type": "application/json",
|
|
841
|
-
"X-User-Timezone": options.timeZone ?? this.userTimezone
|
|
842
|
-
},
|
|
843
|
-
body: JSON.stringify({
|
|
844
|
-
options: { userLocale: "en-US" },
|
|
845
|
-
filterCriteria,
|
|
846
|
-
sortingOption,
|
|
847
|
-
xAxisFields: groupByFields,
|
|
848
|
-
yAxisFields
|
|
849
|
-
})
|
|
850
|
-
});
|
|
851
|
-
if (!res.ok) throw new Error(`Softr datasource chartData failed for "${this.id}": ${res.status}`);
|
|
852
|
-
const payload = await res.json();
|
|
853
|
-
const executionTimestamp = /* @__PURE__ */ new Date(payload.executionTimestamp * 1e3);
|
|
854
|
-
const aliases = {};
|
|
855
|
-
const multipleMetrics = Object.keys(metrics).length > 1;
|
|
856
|
-
for (const [alias, { field, aggregation }] of Object.entries(metrics)) if (aggregation === "COUNT") aliases[COUNT_CNT] = alias;
|
|
857
|
-
else {
|
|
858
|
-
const fieldId = field ? select?.[field] ?? field : field;
|
|
859
|
-
if (fieldId) {
|
|
860
|
-
const suffix = multipleMetrics ? `_${aggregation.toLowerCase()}` : "";
|
|
861
|
-
aliases[fieldId + suffix] = alias;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
for (const field of Array.isArray(groupBy) ? groupBy : [groupBy]) aliases[select?.[field] ?? field] = field;
|
|
865
|
-
return {
|
|
866
|
-
data: payload.data.map((item) => {
|
|
867
|
-
const row = {};
|
|
868
|
-
for (const [rawKey, alias] of Object.entries(aliases)) if (rawKey in item) row[alias] = item[rawKey];
|
|
869
|
-
return row;
|
|
870
|
-
}),
|
|
871
|
-
executionTimestamp
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
function aliasFields(fields, aliases) {
|
|
876
|
-
const result = {};
|
|
877
|
-
for (const [alias, fieldId] of Object.entries(aliases)) result[alias] = fields[fieldId];
|
|
878
|
-
return result;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
//#endregion
|
|
882
|
-
//#region src/core/integration.ts
|
|
883
|
-
var IntegrationAPIImpl = class {
|
|
884
|
-
id;
|
|
885
|
-
request;
|
|
886
|
-
constructor(id, fetch$1) {
|
|
887
|
-
this.id = id;
|
|
888
|
-
this.request = fetch$1;
|
|
889
|
-
}
|
|
890
|
-
async fetch(input, init) {
|
|
891
|
-
const req = new Request(input, init);
|
|
892
|
-
if (req.signal.aborted) throw req.signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
893
|
-
const headers = {};
|
|
894
|
-
new Headers(req.headers).forEach((value, key) => {
|
|
895
|
-
headers[key] = value;
|
|
896
|
-
});
|
|
897
|
-
const payload = {
|
|
898
|
-
method: req.method,
|
|
899
|
-
url: req.url,
|
|
900
|
-
headers
|
|
901
|
-
};
|
|
902
|
-
const method = req.method.toUpperCase();
|
|
903
|
-
if (method !== "GET" && method !== "HEAD" && req.body !== null) payload.body = await req.clone().text();
|
|
904
|
-
return this.request(`/api/integrations/${this.id}/proxy`, {
|
|
905
|
-
method: "POST",
|
|
906
|
-
headers: { "Content-Type": "application/json" },
|
|
907
|
-
body: JSON.stringify(payload),
|
|
908
|
-
signal: req.signal
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
//#endregion
|
|
914
|
-
//#region src/core/workflow.ts
|
|
915
|
-
var WorkflowAPIImpl = class {
|
|
916
|
-
id;
|
|
917
|
-
fetch;
|
|
918
|
-
constructor(id, fetch$1) {
|
|
919
|
-
this.id = id;
|
|
920
|
-
this.fetch = fetch$1;
|
|
921
|
-
}
|
|
922
|
-
async trigger(payload) {
|
|
923
|
-
const res = await this.fetch(`/api/workflows/${this.id}/trigger`, {
|
|
924
|
-
method: "POST",
|
|
925
|
-
...payload ? {
|
|
926
|
-
headers: { "Content-Type": "application/json" },
|
|
927
|
-
body: JSON.stringify(payload)
|
|
928
|
-
} : {}
|
|
929
|
-
});
|
|
930
|
-
if (!res.ok) throw new Error(`Softr workflow trigger failed: ${res.status}`);
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
|
-
|
|
934
|
-
//#endregion
|
|
935
|
-
//#region src/core/sdk.ts
|
|
936
|
-
var SoftrSDKImpl = class {
|
|
937
|
-
baseUrl;
|
|
938
|
-
userTimezone;
|
|
939
|
-
extraHeaders;
|
|
940
|
-
cachedMe = null;
|
|
941
|
-
constructor(config) {
|
|
942
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
943
|
-
this.userTimezone = config.userTimezone ?? import.meta.env.VITE_APP_TIMEZONE ?? "UTC";
|
|
944
|
-
this.extraHeaders = config.headers;
|
|
945
|
-
}
|
|
946
|
-
async me() {
|
|
947
|
-
if (this.cachedMe) return this.cachedMe;
|
|
948
|
-
const res = await this._fetch("/me");
|
|
949
|
-
if (!res.ok) throw new Error(`Softr /me failed: ${res.status}`);
|
|
950
|
-
this.cachedMe = await res.json();
|
|
951
|
-
return this.cachedMe;
|
|
952
|
-
}
|
|
953
|
-
auth = {
|
|
954
|
-
login: async ({ email, password }) => {
|
|
955
|
-
const res = await this._fetch("/auth/login", {
|
|
956
|
-
method: "POST",
|
|
957
|
-
headers: { "Content-Type": "application/json" },
|
|
958
|
-
body: JSON.stringify({
|
|
959
|
-
email,
|
|
960
|
-
password
|
|
961
|
-
})
|
|
962
|
-
});
|
|
963
|
-
if (!res.ok) throw new Error(`Login failed: ${res.status}`);
|
|
964
|
-
this.cachedMe = null;
|
|
965
|
-
},
|
|
966
|
-
logout: async () => {
|
|
967
|
-
const res = await this._fetch("/auth/logout", { method: "POST" });
|
|
968
|
-
if (!res.ok) throw new Error(`Logout failed: ${res.status}`);
|
|
969
|
-
this.cachedMe = null;
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
datasource(id) {
|
|
973
|
-
return new DatasourceAPIImpl(id, this._fetch.bind(this), this.userTimezone);
|
|
974
|
-
}
|
|
975
|
-
integration(id) {
|
|
976
|
-
return new IntegrationAPIImpl(id, this._fetch.bind(this));
|
|
977
|
-
}
|
|
978
|
-
workflow(id) {
|
|
979
|
-
return new WorkflowAPIImpl(id, this._fetch.bind(this));
|
|
980
|
-
}
|
|
981
|
-
_fetch(path, init) {
|
|
982
|
-
const headers = new Headers(this.extraHeaders);
|
|
983
|
-
if (init?.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v));
|
|
984
|
-
const isServer = typeof window === "undefined";
|
|
985
|
-
return fetch(`${this.baseUrl}${path}`, {
|
|
986
|
-
...init,
|
|
987
|
-
credentials: isServer ? "omit" : "include",
|
|
988
|
-
headers
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
};
|
|
992
|
-
function createSoftrSDKFromRequest(request, baseUrl) {
|
|
993
|
-
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
994
|
-
return new SoftrSDKImpl({
|
|
995
|
-
baseUrl,
|
|
996
|
-
userTimezone: parseCookie(cookieHeader, "user-timezone"),
|
|
997
|
-
headers: { cookie: cookieHeader }
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
function parseCookie(cookieHeader, name) {
|
|
1001
|
-
return cookieHeader.split(";").map((c) => c.trim().split("=")).find(([k]) => k === name)?.[1];
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region src/core/middleware.ts
|
|
1006
|
-
/**
|
|
1007
|
-
* TanStack Start middleware. Injects a per-request Softr SDK instance with
|
|
1008
|
-
* cookies forwarded automatically. The base URL is read from VITE_SOFTR_BASE_URL.
|
|
1009
|
-
*
|
|
1010
|
-
* @example
|
|
1011
|
-
* import { withSDK } from '@softr/sdk'
|
|
1012
|
-
*
|
|
1013
|
-
* export const getProjects = createServerFn()
|
|
1014
|
-
* .middleware([withSDK])
|
|
1015
|
-
* .handler(({ context }) => context.sdk.datasource('ds_projects').list())
|
|
1016
|
-
*/
|
|
1017
|
-
const withSDK = createMiddleware().server(({ next, request }) => next({ context: { sdk: createSoftrSDKFromRequest(request, import.meta.env.VITE_SOFTR_BASE_URL) } }));
|
|
1018
|
-
|
|
1019
|
-
//#endregion
|
|
1020
|
-
export { defineVisibility, getSoftrUser, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, q, softrAuthMiddleware, withSDK };
|
|
5
|
+
export { defineVisibility, getSoftrContext, initialSoftrContext, isAccessError, isAlreadySignedInError, isForbiddenError, isUnauthenticatedError, q, softrAuthMiddleware, useCurrentUser, usePermissions };
|