@indietabletop/appkit 0.2.0 → 0.2.1

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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Appends " (Copy)" to the end of the input string if it doesn't already end
3
+ * with " (Copy)", otherwise it appends a number after "Copy", incrementing it
4
+ * if necessary.
5
+ */
6
+ export declare function appendCopyToText(input: string): string;
7
+ /**
8
+ * Works like {@link appendCopyToText}, but ignores empty strings.
9
+ */
10
+ export declare function maybeAppendCopyToText(input: string): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Appends " (Copy)" to the end of the input string if it doesn't already end
3
+ * with " (Copy)", otherwise it appends a number after "Copy", incrementing it
4
+ * if necessary.
5
+ */
6
+ export function appendCopyToText(input) {
7
+ const regex = /^(?<value>.*) \(Copy(?: (?<count>\d+))?\)$/;
8
+ const match = input.match(regex);
9
+ // If there isn't a match, we directly append to the input.
10
+ if (!match) {
11
+ return `${input.trim()} (Copy)`;
12
+ }
13
+ const { value, count } = match.groups ?? {};
14
+ // If `count` capturing group is not present, it means that the input ends
15
+ // with the copy suffix, but it doesn't contain count.
16
+ const nextCount = !count ? 2 : parseInt(count, 10) + 1;
17
+ return `${value.trim()} (Copy ${nextCount})`;
18
+ }
19
+ /**
20
+ * Works like {@link appendCopyToText}, but ignores empty strings.
21
+ */
22
+ export function maybeAppendCopyToText(input) {
23
+ // If input is falsy (i.e. empty string) then we don't want to append
24
+ // anything to it.
25
+ if (!input) {
26
+ return "";
27
+ }
28
+ return appendCopyToText(input);
29
+ }
@@ -0,0 +1,75 @@
1
+ type Falsy = null | undefined | false | 0 | 0n | "";
2
+ type Truthy<T> = Exclude<T, Falsy>;
3
+ interface Operation<SuccessValue, FailureValue> {
4
+ readonly type: "SUCCESS" | "FAILURE" | "PENDING";
5
+ readonly isPending: boolean;
6
+ readonly isSuccess: boolean;
7
+ readonly isFailure: boolean;
8
+ val: SuccessValue | FailureValue | null;
9
+ valueOrNull(): SuccessValue | null;
10
+ valueOrThrow(): SuccessValue;
11
+ hasTruthyValue(): boolean;
12
+ failureValueOrNull(): FailureValue | null;
13
+ failureValueOrThrow(): FailureValue;
14
+ flatMap<T extends AsyncOp<unknown, unknown>>(mappingFn: (value: SuccessValue) => T): T | Failure<FailureValue> | Pending;
15
+ mapSuccess<MappedSuccess>(mappingFn: (value: SuccessValue) => MappedSuccess): Operation<MappedSuccess, FailureValue>;
16
+ mapFailure<MappedFailure>(mappingFn: (value: FailureValue) => MappedFailure): Operation<SuccessValue, MappedFailure>;
17
+ unpack<S, F, P>(mapS: (value: SuccessValue) => S, mapF: (failure: FailureValue) => F, mapP: () => P): S | F | P;
18
+ }
19
+ export declare class Pending implements Operation<never, never> {
20
+ readonly type: "PENDING";
21
+ readonly isPending: true;
22
+ readonly isSuccess: false;
23
+ readonly isFailure: false;
24
+ val: null;
25
+ valueOrNull(): null;
26
+ valueOrThrow(): never;
27
+ hasTruthyValue(): false;
28
+ failureValueOrNull(): null;
29
+ failureValueOrThrow(): never;
30
+ flatMap(): Pending;
31
+ mapSuccess(): Pending;
32
+ mapFailure(): Pending;
33
+ unpack<S, F, P>(_mapS: (value: never) => S, _mapF: (failure: never) => F, mapP: () => P): S | F | P;
34
+ }
35
+ export declare class Success<SuccessValue> implements Operation<SuccessValue, never> {
36
+ readonly type: "SUCCESS";
37
+ readonly isPending: false;
38
+ readonly isSuccess: true;
39
+ readonly isFailure: false;
40
+ readonly value: SuccessValue;
41
+ readonly val: SuccessValue;
42
+ constructor(value: SuccessValue);
43
+ valueOrNull(): SuccessValue;
44
+ valueOrThrow(): SuccessValue;
45
+ hasTruthyValue(): this is Success<Truthy<SuccessValue>>;
46
+ failureValueOrNull(): null;
47
+ failureValueOrThrow(): never;
48
+ flatMap<T extends AsyncOp<unknown, unknown>>(mappingFn: (value: SuccessValue) => T): T;
49
+ mapSuccess<MappedValue>(mappingFn: (value: SuccessValue) => MappedValue): Success<MappedValue>;
50
+ mapFailure(): Success<SuccessValue>;
51
+ unpack<S, F, P>(mapS: (value: SuccessValue) => S, _mapF: (failure: never) => F, _mapP: () => P): S | F | P;
52
+ }
53
+ export declare class Failure<FailureValue> implements Operation<never, FailureValue> {
54
+ readonly type: "FAILURE";
55
+ readonly isPending: false;
56
+ readonly isSuccess: false;
57
+ readonly isFailure: true;
58
+ readonly failure: FailureValue;
59
+ readonly val: FailureValue;
60
+ constructor(failure: FailureValue);
61
+ valueOrNull(): null;
62
+ valueOrThrow(): never;
63
+ hasTruthyValue(): false;
64
+ failureValueOrNull(): FailureValue;
65
+ failureValueOrThrow(): FailureValue;
66
+ flatMap(): Failure<FailureValue>;
67
+ mapSuccess(): Failure<FailureValue>;
68
+ mapFailure<MappedFailure>(mappingFn: (value: FailureValue) => MappedFailure): Failure<MappedFailure>;
69
+ unpack<S, F, P>(_mapS: (value: never) => S, mapF: (failure: FailureValue) => F, _mapP: () => P): S | F | P;
70
+ }
71
+ export declare function fold<Ops extends readonly AsyncOp<unknown, unknown>[] | []>(ops: Ops): AsyncOp<{
72
+ -readonly [Index in keyof Ops]: Ops[Index] extends AsyncOp<infer S, unknown> ? S : never;
73
+ }, Ops[number] extends AsyncOp<unknown, infer F> ? F : never>;
74
+ export type AsyncOp<SuccessValue, FailureValue> = Pending | Success<SuccessValue> | Failure<FailureValue>;
75
+ export {};
@@ -0,0 +1,209 @@
1
+ export class Pending {
2
+ constructor() {
3
+ Object.defineProperty(this, "type", {
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true,
7
+ value: "PENDING"
8
+ });
9
+ Object.defineProperty(this, "isPending", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: true
14
+ });
15
+ Object.defineProperty(this, "isSuccess", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: false
20
+ });
21
+ Object.defineProperty(this, "isFailure", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: false
26
+ });
27
+ Object.defineProperty(this, "val", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: null
32
+ });
33
+ }
34
+ valueOrNull() {
35
+ return null;
36
+ }
37
+ valueOrThrow() {
38
+ throw new Error(`AsyncOp value was accessed but the op is in Pending state.`);
39
+ }
40
+ hasTruthyValue() {
41
+ return false;
42
+ }
43
+ failureValueOrNull() {
44
+ return null;
45
+ }
46
+ failureValueOrThrow() {
47
+ throw new Error(`AsyncOp failure value was accessed but the op is in Pending state.`);
48
+ }
49
+ flatMap() {
50
+ return new Pending();
51
+ }
52
+ mapSuccess() {
53
+ return new Pending();
54
+ }
55
+ mapFailure() {
56
+ return new Pending();
57
+ }
58
+ unpack(_mapS, _mapF, mapP) {
59
+ return mapP();
60
+ }
61
+ }
62
+ export class Success {
63
+ constructor(value) {
64
+ Object.defineProperty(this, "type", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: "SUCCESS"
69
+ });
70
+ Object.defineProperty(this, "isPending", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: false
75
+ });
76
+ Object.defineProperty(this, "isSuccess", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: true
81
+ });
82
+ Object.defineProperty(this, "isFailure", {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: false
87
+ });
88
+ Object.defineProperty(this, "value", {
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true,
92
+ value: void 0
93
+ });
94
+ Object.defineProperty(this, "val", {
95
+ enumerable: true,
96
+ configurable: true,
97
+ writable: true,
98
+ value: void 0
99
+ });
100
+ this.value = value;
101
+ this.val = value;
102
+ }
103
+ valueOrNull() {
104
+ return this.value;
105
+ }
106
+ valueOrThrow() {
107
+ return this.value;
108
+ }
109
+ hasTruthyValue() {
110
+ return !!this.value;
111
+ }
112
+ failureValueOrNull() {
113
+ return null;
114
+ }
115
+ failureValueOrThrow() {
116
+ throw new Error(`AsyncOp failure value was accessed but the op is in Success state.`);
117
+ }
118
+ flatMap(mappingFn) {
119
+ return mappingFn(this.value);
120
+ }
121
+ mapSuccess(mappingFn) {
122
+ return new Success(mappingFn(this.value));
123
+ }
124
+ mapFailure() {
125
+ return new Success(this.value);
126
+ }
127
+ unpack(mapS, _mapF, _mapP) {
128
+ return mapS(this.value);
129
+ }
130
+ }
131
+ export class Failure {
132
+ constructor(failure) {
133
+ Object.defineProperty(this, "type", {
134
+ enumerable: true,
135
+ configurable: true,
136
+ writable: true,
137
+ value: "FAILURE"
138
+ });
139
+ Object.defineProperty(this, "isPending", {
140
+ enumerable: true,
141
+ configurable: true,
142
+ writable: true,
143
+ value: false
144
+ });
145
+ Object.defineProperty(this, "isSuccess", {
146
+ enumerable: true,
147
+ configurable: true,
148
+ writable: true,
149
+ value: false
150
+ });
151
+ Object.defineProperty(this, "isFailure", {
152
+ enumerable: true,
153
+ configurable: true,
154
+ writable: true,
155
+ value: true
156
+ });
157
+ Object.defineProperty(this, "failure", {
158
+ enumerable: true,
159
+ configurable: true,
160
+ writable: true,
161
+ value: void 0
162
+ });
163
+ Object.defineProperty(this, "val", {
164
+ enumerable: true,
165
+ configurable: true,
166
+ writable: true,
167
+ value: void 0
168
+ });
169
+ this.failure = failure;
170
+ this.val = failure;
171
+ }
172
+ valueOrNull() {
173
+ return null;
174
+ }
175
+ valueOrThrow() {
176
+ throw new Error(`AsyncOp value was accessed but the op is in Failure state.`);
177
+ }
178
+ hasTruthyValue() {
179
+ return false;
180
+ }
181
+ failureValueOrNull() {
182
+ return this.failure;
183
+ }
184
+ failureValueOrThrow() {
185
+ return this.failure;
186
+ }
187
+ flatMap() {
188
+ return new Failure(this.failure);
189
+ }
190
+ mapSuccess() {
191
+ return new Failure(this.failure);
192
+ }
193
+ mapFailure(mappingFn) {
194
+ return new Failure(mappingFn(this.failure));
195
+ }
196
+ unpack(_mapS, mapF, _mapP) {
197
+ return mapF(this.failure);
198
+ }
199
+ }
200
+ export function fold(ops) {
201
+ if (ops.every((v) => v.isSuccess)) {
202
+ return new Success(ops.map((op) => op.value));
203
+ }
204
+ const firstFail = ops.find((op) => op.isFailure);
205
+ if (firstFail) {
206
+ return firstFail;
207
+ }
208
+ return new Pending();
209
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Safely returns a string from an unknown value.
3
+ *
4
+ * This function is intended to be used when handling values caught
5
+ * in try/catch blocks.
6
+ *
7
+ * In JS/TS, unless errors are returned, they cannot be properly typed
8
+ * because any code within the try block can throw. Additionally, thrown
9
+ * values do not have to be Errors.
10
+ *
11
+ * This function makes sure to first assert if an Error was caught, and
12
+ * if so, returns its message. If a string was caught, it returns that.
13
+ * Otherwise, it returns "Unknown error".
14
+ */
15
+ export declare function caughtValueToString(value: unknown): string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Safely returns a string from an unknown value.
3
+ *
4
+ * This function is intended to be used when handling values caught
5
+ * in try/catch blocks.
6
+ *
7
+ * In JS/TS, unless errors are returned, they cannot be properly typed
8
+ * because any code within the try block can throw. Additionally, thrown
9
+ * values do not have to be Errors.
10
+ *
11
+ * This function makes sure to first assert if an Error was caught, and
12
+ * if so, returns its message. If a string was caught, it returns that.
13
+ * Otherwise, it returns "Unknown error".
14
+ */
15
+ export function caughtValueToString(value) {
16
+ if (value instanceof Error) {
17
+ return value.message;
18
+ }
19
+ if (typeof value === "string") {
20
+ return value;
21
+ }
22
+ return "Unknown error.";
23
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Combines a list of strings into a single string. Falsy values are ignored.
3
+ */
4
+ export declare function classNames(...classNames: (string | false | null | undefined)[]): string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Combines a list of strings into a single string. Falsy values are ignored.
3
+ */
4
+ export function classNames(...classNames) {
5
+ return classNames.filter((c) => !!c).join(" ");
6
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "scripts": {
8
- "release": "np",
8
+ "release": "npm run build && np",
9
9
  "build": "tsc",
10
10
  "test": "vitest"
11
11
  },