@webamoki/web-svelte 1.2.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/components/showcase/CodeBlock.svelte +1 -1
- package/dist/{components → shared/components}/form/Button.svelte +2 -2
- package/dist/{components → shared/components}/form/Button.svelte.d.ts +2 -2
- package/dist/{components → shared/components}/form/Errors.svelte +1 -1
- package/dist/{components → shared/components}/form/FieldWrapper.svelte +1 -1
- package/dist/{components → shared/components}/form/fields/ChoiceMultiField.svelte +3 -1
- package/dist/{components → shared/components}/form/fields/DateField.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/MessageField.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/NumberField.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/PasswordField.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/SelectField.svelte +3 -3
- package/dist/{components → shared/components}/form/fields/SelectMultiField.svelte +3 -3
- package/dist/{components → shared/components}/form/fields/TextField.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/TextFieldNullable.svelte +2 -2
- package/dist/{components → shared/components}/form/fields/TimeField.svelte +2 -2
- package/dist/{components → shared/components}/ui/choice/ChoiceInternal.svelte +1 -1
- package/dist/{components → shared/components}/ui/choice/WeekdayChoice.svelte +6 -3
- package/dist/{components → shared/components}/ui/choice/WeekdayChoice.svelte.d.ts +1 -1
- package/dist/{components → shared/components}/ui/choice/WeekdayChoiceMulti.svelte +6 -3
- package/dist/{components → shared/components}/ui/choice/WeekdayChoiceMulti.svelte.d.ts +1 -1
- package/dist/{components → shared/components}/ui/context-menu/ContextMenuContent.svelte +1 -1
- package/dist/{components → shared/components}/ui/context-menu/ContextMenuItem.svelte +1 -1
- package/dist/{components → shared/components}/ui/search/SearchBar.svelte +2 -2
- package/dist/{utils/types → shared/utils}/arktype.d.ts +4 -12
- package/dist/shared/utils/arktype.js +40 -0
- package/dist/{utils → shared/utils}/datetime/datetime.spec.js +31 -18
- package/dist/{utils → shared/utils}/datetime/index.d.ts +22 -14
- package/dist/{utils → shared/utils}/datetime/index.js +44 -32
- package/dist/{utils → shared/utils}/email/README.md +5 -5
- package/dist/{utils → shared/utils}/email/ses.js +17 -9
- package/dist/shared/utils/functional/index.d.ts +2 -0
- package/dist/shared/utils/functional/index.js +2 -0
- package/dist/shared/utils/functional/result.d.ts +72 -0
- package/dist/shared/utils/functional/result.js +86 -0
- package/dist/shared/utils/functional/result.spec.d.ts +1 -0
- package/dist/shared/utils/functional/result.spec.js +96 -0
- package/dist/shared/utils/remote.d.ts +28 -0
- package/dist/shared/utils/remote.js +74 -0
- package/dist/{utils/search.d.ts → shared/utils/string.d.ts} +1 -0
- package/dist/{utils/search.js → shared/utils/string.js} +13 -4
- package/package.json +27 -30
- package/dist/utils/email/aws-signer.d.ts +0 -17
- package/dist/utils/email/aws-signer.js +0 -83
- package/dist/utils/string.d.ts +0 -1
- package/dist/utils/string.js +0 -4
- package/dist/utils/types/arktype.js +0 -92
- package/dist/utils/types/consts.d.ts +0 -5
- package/dist/utils/types/consts.js +0 -5
- package/dist/utils/types/db.d.ts +0 -57
- package/dist/utils/types/db.js +0 -34
- /package/dist/{components → shared/components}/form/Errors.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/FieldWrapper.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/Form.svelte +0 -0
- /package/dist/{components → shared/components}/form/Form.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/IconInputWrapper.svelte +0 -0
- /package/dist/{components → shared/components}/form/IconInputWrapper.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/ChoiceField.svelte +0 -0
- /package/dist/{components → shared/components}/form/fields/ChoiceField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/ChoiceMultiField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/DateField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/HexColorField.svelte +0 -0
- /package/dist/{components → shared/components}/form/fields/HexColorField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/MessageField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/NumberField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/PasswordField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/SelectField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/SelectMultiField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/TextField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/TextFieldNullable.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/TimeField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/WeekdayChoiceField.svelte +0 -0
- /package/dist/{components → shared/components}/form/fields/WeekdayChoiceField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/fields/WeekdayChoiceMultiField.svelte +0 -0
- /package/dist/{components → shared/components}/form/fields/WeekdayChoiceMultiField.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/form/index.d.ts +0 -0
- /package/dist/{components → shared/components}/form/index.js +0 -0
- /package/dist/{components → shared/components}/ui/choice/Choice.svelte +0 -0
- /package/dist/{components → shared/components}/ui/choice/Choice.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/choice/ChoiceInternal.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/choice/ChoiceMulti.svelte +0 -0
- /package/dist/{components → shared/components}/ui/choice/ChoiceMulti.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenu.svelte +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenu.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuContent.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuItem.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuSeparator.svelte +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuSeparator.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuTrigger.svelte +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/ContextMenuTrigger.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/context-menu-state.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/context-menu/context-menu-state.svelte.js +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/Draggable.svelte +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/Draggable.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/Dropzone.svelte +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/Dropzone.svelte.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/drag-manager.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/drag-drop/drag-manager.js +0 -0
- /package/dist/{components → shared/components}/ui/index.d.ts +0 -0
- /package/dist/{components → shared/components}/ui/index.js +0 -0
- /package/dist/{components → shared/components}/ui/search/SearchBar.svelte.d.ts +0 -0
- /package/dist/{server → shared/server}/form-handler.d.ts +0 -0
- /package/dist/{server → shared/server}/form-handler.js +0 -0
- /package/dist/{server → shared/server}/form-processor.d.ts +0 -0
- /package/dist/{server → shared/server}/form-processor.js +0 -0
- /package/dist/{utils → shared/utils}/datetime/datetime.spec.d.ts +0 -0
- /package/dist/{utils → shared/utils}/email/index.d.ts +0 -0
- /package/dist/{utils → shared/utils}/email/index.js +0 -0
- /package/dist/{utils → shared/utils}/email/ses.d.ts +0 -0
- /package/dist/{utils → shared/utils}/email/ses.test.d.ts +0 -0
- /package/dist/{utils → shared/utils}/email/ses.test.js +0 -0
- /package/dist/{utils → shared/utils}/form/index.d.ts +0 -0
- /package/dist/{utils → shared/utils}/form/index.js +0 -0
- /package/dist/{utils → shared/utils}/form/virtual-form.d.ts +0 -0
- /package/dist/{utils → shared/utils}/form/virtual-form.js +0 -0
- /package/dist/{highlight.d.ts → utils/highlight.d.ts} +0 -0
- /package/dist/{highlight.js → utils/highlight.js} +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { CalendarDate, DateFormatter, fromDate, getDayOfWeek, getLocalTimeZone, startOfMonth, Time, toCalendarDate, today, toTime, ZonedDateTime } from '@internationalized/date';
|
|
1
|
+
import { CalendarDate, DateFormatter, fromDate, getDayOfWeek, getLocalTimeZone, startOfMonth, Time, toCalendarDate, today as todayFn, toTime, ZonedDateTime } from '@internationalized/date';
|
|
2
2
|
import { map, range } from 'ramda';
|
|
3
|
-
const DEFAULT_TIME_ZONE = 'Europe/London';
|
|
4
3
|
const DEFAULT_LOCALE = 'en-GB';
|
|
5
4
|
// Day of the week
|
|
6
5
|
export const Days = [
|
|
@@ -13,25 +12,46 @@ export const Days = [
|
|
|
13
12
|
'Sunday'
|
|
14
13
|
];
|
|
15
14
|
export const DayIndex = Object.fromEntries(Days.map((day, index) => [day, index]));
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
// Date functions local to a timezone
|
|
16
|
+
export class LocalDateF {
|
|
17
|
+
timezone;
|
|
18
|
+
constructor(timezone) {
|
|
19
|
+
this.timezone = timezone;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Calculates the age from a date of birth.
|
|
23
|
+
* @param dob - The date of birth.
|
|
24
|
+
* @returns The age in years.
|
|
25
|
+
* @throws Error if the date of birth is in the future.
|
|
26
|
+
*/
|
|
27
|
+
ageFromDob(dob) {
|
|
28
|
+
const todayDate = this.today();
|
|
29
|
+
if (todayDate.compare(dob) < 0) {
|
|
30
|
+
throw new Error('Date of birth is in the future');
|
|
31
|
+
}
|
|
32
|
+
let years = todayDate.year - dob.year;
|
|
33
|
+
const monthDiff = todayDate.month - dob.month;
|
|
34
|
+
const dayDiff = todayDate.day - dob.day;
|
|
35
|
+
// Adjust years down if birthday hasn't occurred this year
|
|
36
|
+
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
|
|
37
|
+
years--;
|
|
38
|
+
}
|
|
39
|
+
return years;
|
|
26
40
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a given date is today.
|
|
43
|
+
* @param date - The date to check.
|
|
44
|
+
* @returns True if the date is today, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
isDateToday(date) {
|
|
47
|
+
return this.today().compare(date) === 0;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @returns The current date.
|
|
51
|
+
*/
|
|
52
|
+
today() {
|
|
53
|
+
return todayFn(this.timezone);
|
|
33
54
|
}
|
|
34
|
-
return years;
|
|
35
55
|
}
|
|
36
56
|
/**
|
|
37
57
|
* Checks if two time ranges overlap, boundaries are not considered overlapping.
|
|
@@ -145,14 +165,6 @@ export function isDateDay(date, dayOfWeek) {
|
|
|
145
165
|
const dateDay = getDayOfDate(date);
|
|
146
166
|
return dateDay === dayOfWeek;
|
|
147
167
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Checks if a given date is today.
|
|
150
|
-
* @param date - The date to check.
|
|
151
|
-
* @returns True if the date is today, false otherwise.
|
|
152
|
-
*/
|
|
153
|
-
export function isDateToday(date, timezone) {
|
|
154
|
-
return today(timezone).compare(date) === 0;
|
|
155
|
-
}
|
|
156
168
|
const msPerWeek = 7 * 24 * 60 * 60 * 1000;
|
|
157
169
|
/**
|
|
158
170
|
* Calculates the difference in weeks between two dates.
|
|
@@ -160,9 +172,10 @@ const msPerWeek = 7 * 24 * 60 * 60 * 1000;
|
|
|
160
172
|
* @param date2 - The second date in order.
|
|
161
173
|
*/
|
|
162
174
|
export function dateDiffWeeks(date1, date2) {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
175
|
+
const date1Ms = date1.toDate('UTC').getTime();
|
|
176
|
+
const date2Ms = date2.toDate('UTC').getTime();
|
|
177
|
+
const absDiff = Math.abs(date2Ms - date1Ms);
|
|
178
|
+
return Math.floor(absDiff / msPerWeek);
|
|
166
179
|
}
|
|
167
180
|
/* Formatting */
|
|
168
181
|
/* Day of the week*/
|
|
@@ -313,9 +326,8 @@ function formatDate(date, formatter) {
|
|
|
313
326
|
}
|
|
314
327
|
// Pad number with zeroes to the left
|
|
315
328
|
function padNum(num, len) {
|
|
316
|
-
if (isNaN(num))
|
|
329
|
+
if (isNaN(num))
|
|
317
330
|
return '0'.repeat(len);
|
|
318
|
-
}
|
|
319
331
|
return num.toString().padStart(len, '0');
|
|
320
332
|
}
|
|
321
333
|
// SerDe
|
|
@@ -4,17 +4,17 @@ This package provides utilities for sending emails using AWS Simple Email Servic
|
|
|
4
4
|
|
|
5
5
|
## Key Features
|
|
6
6
|
|
|
7
|
-
- **Cloudflare Workers Compatible**: Uses direct AWS SES API calls
|
|
8
|
-
- **AWS Signature V4**:
|
|
7
|
+
- **Cloudflare Workers Compatible**: Uses `aws4fetch` for direct AWS SES API calls
|
|
8
|
+
- **AWS Signature V4**: Handled by `aws4fetch` for secure API requests
|
|
9
9
|
- **Zero Heavy Dependencies**: No AWS SDK required, works in any JavaScript runtime with fetch support
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The implementation uses:
|
|
14
14
|
|
|
15
|
+
- `aws4fetch` for AWS Signature V4 signing and requests
|
|
15
16
|
- Native `fetch` API for HTTP requests
|
|
16
|
-
-
|
|
17
|
-
- Native `DOMParser` for XML response parsing
|
|
17
|
+
- Native `DOMParser` for XML response parsing (optional, used for error/response parsing)
|
|
18
18
|
|
|
19
19
|
## Configuration
|
|
20
20
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AwsClient } from 'aws4fetch';
|
|
2
2
|
/**
|
|
3
3
|
* Send an email using AWS SES API.
|
|
4
4
|
* Uses AWS Signature V4 signing and fetch API for Cloudflare Workers compatibility.
|
|
@@ -71,21 +71,29 @@ export async function sendEmail(options) {
|
|
|
71
71
|
const host = `email.${awsRegion}.amazonaws.com`;
|
|
72
72
|
const path = '/';
|
|
73
73
|
try {
|
|
74
|
-
|
|
75
|
-
const { headers } = await signRequest('POST', host, path, body, {
|
|
74
|
+
const aws = new AwsClient({
|
|
76
75
|
accessKeyId: awsAccessKeyId,
|
|
77
76
|
region: awsRegion,
|
|
78
|
-
secretAccessKey: awsSecretAccessKey
|
|
77
|
+
secretAccessKey: awsSecretAccessKey,
|
|
78
|
+
service: 'ses'
|
|
79
79
|
});
|
|
80
|
-
|
|
81
|
-
const
|
|
80
|
+
const url = `https://${host}${path}`;
|
|
81
|
+
const init = {
|
|
82
82
|
body,
|
|
83
83
|
headers: {
|
|
84
|
-
|
|
85
|
-
'Content-Length': body.length.toString(),
|
|
86
|
-
Host: host
|
|
84
|
+
'Content-Type': 'application/x-form-urlencoded'
|
|
87
85
|
},
|
|
88
86
|
method: 'POST'
|
|
87
|
+
};
|
|
88
|
+
// Sign the request and then use the global fetch.
|
|
89
|
+
// We avoid using aws.fetch() directly as it can cause issues in some test environments
|
|
90
|
+
// or when fetch is mocked/replaced after AwsClient instantiation.
|
|
91
|
+
const signedRequest = await aws.sign(url, init);
|
|
92
|
+
// Use two-argument fetch to satisfy tests that expect the second argument to be an options object
|
|
93
|
+
const response = await fetch(signedRequest.url, {
|
|
94
|
+
body: init.body,
|
|
95
|
+
headers: signedRequest.headers,
|
|
96
|
+
method: signedRequest.method
|
|
89
97
|
});
|
|
90
98
|
const responseText = await response.text();
|
|
91
99
|
if (!response.ok) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type Result<T, E> = {
|
|
2
|
+
error: E;
|
|
3
|
+
ok: false;
|
|
4
|
+
} | {
|
|
5
|
+
ok: true;
|
|
6
|
+
value: T;
|
|
7
|
+
};
|
|
8
|
+
export declare const Result: {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a failed result with the given error.
|
|
11
|
+
*/
|
|
12
|
+
err<E>(error: E): {
|
|
13
|
+
error: E;
|
|
14
|
+
ok: false;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Creates a successful result with the given value.
|
|
18
|
+
*/
|
|
19
|
+
ok<T>(value: T): {
|
|
20
|
+
ok: true;
|
|
21
|
+
value: T;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the result is Ok.
|
|
25
|
+
*/
|
|
26
|
+
isOk<T, E>(result: Result<T, E>): result is {
|
|
27
|
+
ok: true;
|
|
28
|
+
value: T;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Returns true if the result is Err.
|
|
32
|
+
*/
|
|
33
|
+
isErr<T, E>(result: Result<T, E>): result is {
|
|
34
|
+
error: E;
|
|
35
|
+
ok: false;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Returns the contained Ok value or a provided default.
|
|
39
|
+
*/
|
|
40
|
+
unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the contained Ok value or computes it from the provided op.
|
|
43
|
+
* @param op - Computes value in error case or throws an error.
|
|
44
|
+
*/
|
|
45
|
+
unwrapOrElse<T, E>(result: Result<T, E>, op: (error: E) => T): T;
|
|
46
|
+
/**
|
|
47
|
+
* Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value,
|
|
48
|
+
* leaving an Err value untouched.
|
|
49
|
+
*/
|
|
50
|
+
map<T, U, E>(result: Result<T, E>, fn: (val: T) => U): Result<U, E>;
|
|
51
|
+
/**
|
|
52
|
+
* Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value,
|
|
53
|
+
* leaving an Ok value untouched.
|
|
54
|
+
*/
|
|
55
|
+
mapErr<T, E, F>(result: Result<T, E>, fn: (err: E) => F): Result<T, F>;
|
|
56
|
+
/**
|
|
57
|
+
* Calls op if the result is Ok, otherwise returns the Err value of self.
|
|
58
|
+
* This function can be used for control flow based on Result values.
|
|
59
|
+
*/
|
|
60
|
+
andThen<T, U, E>(result: Result<T, E>, op: (val: T) => Result<U, E>): Result<U, E>;
|
|
61
|
+
/**
|
|
62
|
+
* Calls op if the result is Err, otherwise returns the Ok value of self.
|
|
63
|
+
*/
|
|
64
|
+
orElse<T, E, F>(result: Result<T, E>, op: (err: E) => Result<T, F>): Result<T, F>;
|
|
65
|
+
/**
|
|
66
|
+
* Pattern matches the Result and returns the result of the corresponding arm.
|
|
67
|
+
*/
|
|
68
|
+
match<T, E, R>(result: Result<T, E>, arms: {
|
|
69
|
+
err: (err: E) => R;
|
|
70
|
+
ok: (val: T) => R;
|
|
71
|
+
}): R;
|
|
72
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const Result = {
|
|
2
|
+
/**
|
|
3
|
+
* Creates a failed result with the given error.
|
|
4
|
+
*/
|
|
5
|
+
err(error) {
|
|
6
|
+
return { error, ok: false };
|
|
7
|
+
},
|
|
8
|
+
/**
|
|
9
|
+
* Creates a successful result with the given value.
|
|
10
|
+
*/
|
|
11
|
+
ok(value) {
|
|
12
|
+
return { ok: true, value };
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* Returns true if the result is Ok.
|
|
16
|
+
*/
|
|
17
|
+
isOk(result) {
|
|
18
|
+
return result.ok;
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the result is Err.
|
|
22
|
+
*/
|
|
23
|
+
isErr(result) {
|
|
24
|
+
return !result.ok;
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* Returns the contained Ok value or a provided default.
|
|
28
|
+
*/
|
|
29
|
+
unwrapOr(result, defaultValue) {
|
|
30
|
+
if (result.ok)
|
|
31
|
+
return result.value;
|
|
32
|
+
return defaultValue;
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Returns the contained Ok value or computes it from the provided op.
|
|
36
|
+
* @param op - Computes value in error case or throws an error.
|
|
37
|
+
*/
|
|
38
|
+
unwrapOrElse(result, op) {
|
|
39
|
+
if (result.ok)
|
|
40
|
+
return result.value;
|
|
41
|
+
return op(result.error);
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value,
|
|
45
|
+
* leaving an Err value untouched.
|
|
46
|
+
*/
|
|
47
|
+
map(result, fn) {
|
|
48
|
+
if (result.ok)
|
|
49
|
+
return Result.ok(fn(result.value));
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Maps a Result<T, E> to Result<T, F> by applying a function to a contained Err value,
|
|
54
|
+
* leaving an Ok value untouched.
|
|
55
|
+
*/
|
|
56
|
+
mapErr(result, fn) {
|
|
57
|
+
if (!result.ok)
|
|
58
|
+
return Result.err(fn(result.error));
|
|
59
|
+
return result;
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Calls op if the result is Ok, otherwise returns the Err value of self.
|
|
63
|
+
* This function can be used for control flow based on Result values.
|
|
64
|
+
*/
|
|
65
|
+
andThen(result, op) {
|
|
66
|
+
if (result.ok)
|
|
67
|
+
return op(result.value);
|
|
68
|
+
return result;
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Calls op if the result is Err, otherwise returns the Ok value of self.
|
|
72
|
+
*/
|
|
73
|
+
orElse(result, op) {
|
|
74
|
+
if (!result.ok)
|
|
75
|
+
return op(result.error);
|
|
76
|
+
return result;
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* Pattern matches the Result and returns the result of the corresponding arm.
|
|
80
|
+
*/
|
|
81
|
+
match(result, arms) {
|
|
82
|
+
if (result.ok)
|
|
83
|
+
return arms.ok(result.value);
|
|
84
|
+
return arms.err(result.error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Result } from './result.js';
|
|
3
|
+
describe('Result', () => {
|
|
4
|
+
it('ok should create a successful result', () => {
|
|
5
|
+
const res = Result.ok(42);
|
|
6
|
+
expect(res).toEqual({ ok: true, value: 42 });
|
|
7
|
+
expect(Result.isOk(res)).toBe(true);
|
|
8
|
+
expect(Result.isErr(res)).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
it('err should create a failed result', () => {
|
|
11
|
+
const res = Result.err('error');
|
|
12
|
+
expect(res).toEqual({ error: 'error', ok: false });
|
|
13
|
+
expect(Result.isOk(res)).toBe(false);
|
|
14
|
+
expect(Result.isErr(res)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
describe('unwrapOr', () => {
|
|
17
|
+
it('should return value for ok', () => {
|
|
18
|
+
expect(Result.unwrapOr(Result.ok(42), 0)).toBe(42);
|
|
19
|
+
});
|
|
20
|
+
it('should return default value for err', () => {
|
|
21
|
+
expect(Result.unwrapOr(Result.err('error'), 0)).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('unwrapOrElse', () => {
|
|
25
|
+
it('should return value for ok', () => {
|
|
26
|
+
expect(Result.unwrapOrElse(Result.ok(42), () => 0)).toBe(42);
|
|
27
|
+
});
|
|
28
|
+
it('should return computed value for err', () => {
|
|
29
|
+
expect(Result.unwrapOrElse(Result.err('error'), (e) => e.length)).toBe(5);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('map', () => {
|
|
33
|
+
it('should transform ok value', () => {
|
|
34
|
+
const res = Result.map(Result.ok(42), (v) => v * 2);
|
|
35
|
+
expect(res).toEqual(Result.ok(84));
|
|
36
|
+
});
|
|
37
|
+
it('should not transform err value', () => {
|
|
38
|
+
const res = Result.map(Result.err('error'), (v) => v * 2);
|
|
39
|
+
expect(res).toEqual(Result.err('error'));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('mapErr', () => {
|
|
43
|
+
it('should transform err value', () => {
|
|
44
|
+
const res = Result.mapErr(Result.err('error'), (e) => e.toUpperCase());
|
|
45
|
+
expect(res).toEqual(Result.err('ERROR'));
|
|
46
|
+
});
|
|
47
|
+
it('should not transform ok value', () => {
|
|
48
|
+
const res = Result.mapErr(Result.ok(42), (e) => e.toUpperCase());
|
|
49
|
+
expect(res).toEqual(Result.ok(42));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('andThen', () => {
|
|
53
|
+
it('should chain ok values', () => {
|
|
54
|
+
const res = Result.andThen(Result.ok(42), (v) => Result.ok(v * 2));
|
|
55
|
+
expect(res).toEqual(Result.ok(84));
|
|
56
|
+
});
|
|
57
|
+
it('should return first err', () => {
|
|
58
|
+
const res = Result.andThen(Result.err('error'), (v) => Result.ok(v * 2));
|
|
59
|
+
expect(res).toEqual(Result.err('error'));
|
|
60
|
+
});
|
|
61
|
+
it('should return nested err', () => {
|
|
62
|
+
const res = Result.andThen(Result.ok(42), () => Result.err('nested error'));
|
|
63
|
+
expect(res).toEqual(Result.err('nested error'));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('orElse', () => {
|
|
67
|
+
it('should chain err values', () => {
|
|
68
|
+
const res = Result.orElse(Result.err('error'), (e) => Result.err(e.toUpperCase()));
|
|
69
|
+
expect(res).toEqual(Result.err('ERROR'));
|
|
70
|
+
});
|
|
71
|
+
it('should return first ok', () => {
|
|
72
|
+
const res = Result.orElse(Result.ok(42), (e) => Result.err(e.toUpperCase()));
|
|
73
|
+
expect(res).toEqual(Result.ok(42));
|
|
74
|
+
});
|
|
75
|
+
it('should return nested ok', () => {
|
|
76
|
+
const res = Result.orElse(Result.err('error'), () => Result.ok(42));
|
|
77
|
+
expect(res).toEqual(Result.ok(42));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('match', () => {
|
|
81
|
+
it('should call ok branch', () => {
|
|
82
|
+
const res = Result.match(Result.ok(42), {
|
|
83
|
+
err: () => 0,
|
|
84
|
+
ok: (v) => v * 2
|
|
85
|
+
});
|
|
86
|
+
expect(res).toBe(84);
|
|
87
|
+
});
|
|
88
|
+
it('should call err branch', () => {
|
|
89
|
+
const res = Result.match(Result.err('error'), {
|
|
90
|
+
err: (e) => e.length,
|
|
91
|
+
ok: (v) => v * 2
|
|
92
|
+
});
|
|
93
|
+
expect(res).toBe(5);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { type InvalidField, type RemoteCommand, type RemoteForm, type RemoteFormInput, type RemoteQueryFunction } from '@sveltejs/kit';
|
|
3
|
+
import { Result } from './functional/index.js';
|
|
4
|
+
export type CheckFunction = () => Promise<CheckResult>;
|
|
5
|
+
export type CheckResult = Result<void, ResponseError>;
|
|
6
|
+
export declare const CheckResult: {
|
|
7
|
+
ok(): {
|
|
8
|
+
ok: true;
|
|
9
|
+
value: void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type ResponseError = {
|
|
13
|
+
code: number;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
export type ResponseResult<T> = Result<T, ResponseError>;
|
|
17
|
+
export declare const ResponseResult: {
|
|
18
|
+
/**
|
|
19
|
+
* Unwraps a ResponseResult, throwing a sveltekit error if it is an error result.
|
|
20
|
+
*/
|
|
21
|
+
unwrap<T>(result: ResponseResult<T>): T;
|
|
22
|
+
};
|
|
23
|
+
export declare function guardedCommand<Schema extends StandardSchemaV1, Output>(check: CheckFunction, schema: Schema, fn: (output: StandardSchemaV1.InferOutput<Schema>) => Promise<ResponseResult<Output>>): RemoteCommand<StandardSchemaV1.InferInput<Schema>, Promise<ResponseResult<Output>>>;
|
|
24
|
+
export declare function guardedCommandVoid<Output>(check: CheckFunction, fn: () => Promise<ResponseResult<Output>>): RemoteCommand<void, Promise<ResponseResult<Output>>>;
|
|
25
|
+
export declare function guardedForm<Schema extends StandardSchemaV1<RemoteFormInput, Record<string, unknown>>, Output>(schema: Schema, check: CheckFunction, fn: (output: StandardSchemaV1.InferOutput<Schema>, issue: InvalidField<StandardSchemaV1.InferInput<Schema>>) => Promise<Output>): RemoteForm<StandardSchemaV1.InferInput<Schema>, Output>;
|
|
26
|
+
export declare function guardedFormVoid<Output>(check: CheckFunction, fn: () => Promise<Output>): RemoteForm<void, Output>;
|
|
27
|
+
export declare function guardedQuery<Schema extends StandardSchemaV1, Output>(check: CheckFunction, schema: Schema, fn: (output: StandardSchemaV1.InferOutput<Schema>) => Promise<ResponseResult<Output>>): RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, ResponseResult<Output>>;
|
|
28
|
+
export declare function guardedQueryVoid<Output>(check: CheckFunction, fn: () => Promise<ResponseResult<Output>>): RemoteQueryFunction<void, ResponseResult<Output>>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { command, form, query } from '$app/server';
|
|
2
|
+
import { error } from '@sveltejs/kit';
|
|
3
|
+
import { Result } from './functional/index.js';
|
|
4
|
+
export const CheckResult = {
|
|
5
|
+
/* Helper function for ok check result. */
|
|
6
|
+
ok() {
|
|
7
|
+
return Result.ok(undefined);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
export const ResponseResult = {
|
|
11
|
+
/**
|
|
12
|
+
* Unwraps a ResponseResult, throwing a sveltekit error if it is an error result.
|
|
13
|
+
*/
|
|
14
|
+
unwrap(result) {
|
|
15
|
+
return Result.unwrapOrElse(result, (err) => error(err.code, err.message));
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/* Guards command remote function with a check function. */
|
|
19
|
+
export function guardedCommand(check, schema, fn) {
|
|
20
|
+
return command(schema, async (output) => {
|
|
21
|
+
const outcome = await check();
|
|
22
|
+
// Command remote functions cannot redirect for error,
|
|
23
|
+
// so return result object.
|
|
24
|
+
if (!outcome.ok)
|
|
25
|
+
return Result.err(outcome.error);
|
|
26
|
+
return await fn(output);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/* Guards input-less command remote function with a check function. */
|
|
30
|
+
export function guardedCommandVoid(check, fn) {
|
|
31
|
+
return command(async () => {
|
|
32
|
+
const outcome = await check();
|
|
33
|
+
if (!outcome.ok)
|
|
34
|
+
return Result.err(outcome.error);
|
|
35
|
+
return await fn();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/* Guards form remote function with a check function. */
|
|
39
|
+
export function guardedForm(schema, check, fn) {
|
|
40
|
+
return form(schema, async (output, issue) => {
|
|
41
|
+
const outcome = await check();
|
|
42
|
+
// Use sveltekit error to play nicer form errors
|
|
43
|
+
if (!outcome.ok)
|
|
44
|
+
error(outcome.error.code, outcome.error.message);
|
|
45
|
+
return await fn(output, issue);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/* Guards input-less form remote function with a check function. */
|
|
49
|
+
export function guardedFormVoid(check, fn) {
|
|
50
|
+
return form(async () => {
|
|
51
|
+
const outcome = await check();
|
|
52
|
+
if (!outcome.ok)
|
|
53
|
+
error(outcome.error.code, outcome.error.message);
|
|
54
|
+
return await fn();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/* Guards query remote function with a check function. */
|
|
58
|
+
export function guardedQuery(check, schema, fn) {
|
|
59
|
+
return query(schema, async (output) => {
|
|
60
|
+
const outcome = await check();
|
|
61
|
+
if (!outcome.ok)
|
|
62
|
+
return Result.err(outcome.error);
|
|
63
|
+
return await fn(output);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/* Guards input-less query remote function with a check function. */
|
|
67
|
+
export function guardedQueryVoid(check, fn) {
|
|
68
|
+
return query(async () => {
|
|
69
|
+
const outcome = await check();
|
|
70
|
+
if (!outcome.ok)
|
|
71
|
+
return Result.err(outcome.error);
|
|
72
|
+
return await fn();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -23,8 +23,14 @@ export function fuzzySearch(needle, haystack) {
|
|
|
23
23
|
export function fuzzySearchHighlight(needle, haystack) {
|
|
24
24
|
const hlen = haystack.length;
|
|
25
25
|
const nlen = needle.length;
|
|
26
|
+
const escape = (text) => text
|
|
27
|
+
.replace(/&/g, '&')
|
|
28
|
+
.replace(/</g, '<')
|
|
29
|
+
.replace(/>/g, '>')
|
|
30
|
+
.replace(/"/g, '"')
|
|
31
|
+
.replace(/'/g, ''');
|
|
26
32
|
if (nlen === 0)
|
|
27
|
-
return `<span>${haystack}</span>`;
|
|
33
|
+
return `<span>${escape(haystack)}</span>`;
|
|
28
34
|
if (nlen > hlen)
|
|
29
35
|
return null;
|
|
30
36
|
let result = '';
|
|
@@ -37,7 +43,7 @@ export function fuzzySearchHighlight(needle, haystack) {
|
|
|
37
43
|
const hch = haystack[j];
|
|
38
44
|
if (hch.toLowerCase() === nch) {
|
|
39
45
|
// match found → wrap in <b>
|
|
40
|
-
result += `<b>${hch}</b>`;
|
|
46
|
+
result += `<b>${escape(hch)}</b>`;
|
|
41
47
|
j++;
|
|
42
48
|
found = true;
|
|
43
49
|
matchedCount++;
|
|
@@ -45,7 +51,7 @@ export function fuzzySearchHighlight(needle, haystack) {
|
|
|
45
51
|
}
|
|
46
52
|
else {
|
|
47
53
|
// non-match → normal text
|
|
48
|
-
result += hch;
|
|
54
|
+
result += escape(hch);
|
|
49
55
|
j++;
|
|
50
56
|
}
|
|
51
57
|
}
|
|
@@ -55,9 +61,12 @@ export function fuzzySearchHighlight(needle, haystack) {
|
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
// add remaining unmatched characters
|
|
58
|
-
result += haystack.slice(j);
|
|
64
|
+
result += escape(haystack.slice(j));
|
|
59
65
|
// sanity check — must have matched all needle chars
|
|
60
66
|
if (matchedCount < nlen)
|
|
61
67
|
return null;
|
|
62
68
|
return `<span>${result}</span>`;
|
|
63
69
|
}
|
|
70
|
+
export function toTitleCase(str) {
|
|
71
|
+
return str.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase());
|
|
72
|
+
}
|