target-supabase-sdk 0.1.0 → 0.1.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.
package/src/core.api.ts CHANGED
@@ -1,338 +1,338 @@
1
- import { pick } from "lodash-es";
2
- import { createResponse, Target } from "./core.interface";
3
- import { supabase } from ".";
4
- import { BaseValidator, handleSupabaseError } from "./core.utils";
5
- import { PostgrestFilterBuilder } from "@supabase/postgrest-js";
6
-
7
- export interface QueryFilter {
8
- field: string;
9
- operator: "eq";
10
- value: unknown;
11
- }
12
-
13
- export interface QueryOrderBy {
14
- field: string;
15
- ascending: boolean;
16
- }
17
-
18
- export interface BaseQueryParams {
19
- filterList?: QueryFilter[];
20
- orderBy?: QueryOrderBy;
21
- }
22
-
23
- export const getTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
24
- const query =
25
- filterList != null && filterList.length > 0
26
- ? filterList.reduce((query, filter) => {
27
- switch (filter.operator) {
28
- case "eq":
29
- return query.eq(filter.field, filter.value);
30
- default:
31
- return query;
32
- }
33
- }, supabase.client.from("target").select().eq("id", id))
34
- : supabase.client.from("target").select().eq("id", id);
35
-
36
- const { data, error } = await query.single();
37
- if (error) {
38
- handleSupabaseError("getTarget", error, "Failed to fetch target.");
39
- }
40
- return createResponse.success<Target>(data as Target);
41
- };
42
-
43
- export const getPossibleTarget = async ({ filterList }: { filterList: QueryFilter[] }) => {
44
- const query = filterList.reduce((query, filter) => {
45
- switch (filter.operator) {
46
- case "eq":
47
- return query.eq(filter.field, filter.value);
48
- default:
49
- return query;
50
- }
51
- }, supabase.client.from("target").select());
52
-
53
- const { data, error } = await query.maybeSingle();
54
- if (error) {
55
- handleSupabaseError("getPossibleTarget", error, "Failed to fetch target.");
56
- }
57
- return createResponse.success<Target | null>(data);
58
- };
59
-
60
- export interface GetTargetListParams extends BaseQueryParams {
61
- pageNum: number;
62
- pageSize: number;
63
- category: Target["category"];
64
- }
65
-
66
- export const getTargetList = async ({
67
- pageNum,
68
- pageSize,
69
- category,
70
- filterList = [],
71
- orderBy = { field: "created_at", ascending: false },
72
- filterBuilder,
73
- }: GetTargetListParams & {
74
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
75
- filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
76
- }) => {
77
- const query =
78
- filterBuilder ??
79
- filterList.reduce((query, filter) => {
80
- switch (filter.operator) {
81
- case "eq":
82
- return query.eq(filter.field, filter.value);
83
- default:
84
- return query;
85
- }
86
- }, supabase.client.from("target").select("*").eq("category", category));
87
-
88
- const { data, error } = await query
89
- .order(orderBy.field, { ascending: orderBy.ascending })
90
- .range(pageNum * pageSize, (pageNum + 1) * pageSize - 1);
91
-
92
- if (error) {
93
- handleSupabaseError("getTargetList", error, "Failed to fetch target list.");
94
- }
95
- return createResponse.success<Target[]>(data);
96
- };
97
-
98
- export interface GetTargetTotalCountParams extends BaseQueryParams {
99
- category: Target["category"];
100
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
101
- filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
102
- }
103
-
104
- export const getTargetTotalCount = async ({
105
- category,
106
- filterList = [],
107
- filterBuilder,
108
- }: GetTargetTotalCountParams) => {
109
- const query =
110
- filterBuilder ??
111
- filterList.reduce(
112
- (query, filter) => {
113
- switch (filter.operator) {
114
- case "eq":
115
- return query.eq(filter.field, filter.value);
116
- default:
117
- return query;
118
- }
119
- },
120
- supabase.client
121
- .from("target")
122
- .select("id", { count: "exact", head: true }) // head: true means no row payload
123
- .eq("category", category)
124
- );
125
-
126
- const { count, error } = await query;
127
-
128
- if (error) {
129
- handleSupabaseError("getTargetTotalCount", error, "Failed to fetch target count.");
130
- }
131
- return createResponse.success<number>(count ?? 0);
132
- };
133
-
134
- export const deleteTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
135
- // Check whether the target exists given filterList
136
- if (filterList != null && filterList.length > 0) {
137
- const query = filterList.reduce((query, filter) => {
138
- switch (filter.operator) {
139
- case "eq":
140
- return query.eq(filter.field, filter.value);
141
- default:
142
- return query;
143
- }
144
- }, supabase.client.from("target").select().eq("id", id));
145
- const { data: existingTarget, error: existingError } = await query.single();
146
- if (existingError || existingTarget == null) {
147
- const msg = `[deleteTarget] Cannot find the target ${id}`;
148
- console.error(msg, { filterList }, existingError);
149
- throw new Error(msg);
150
- }
151
- }
152
- const { error } = await supabase.client.from("target").delete().eq("id", id);
153
- if (error) {
154
- handleSupabaseError("deleteTarget", error, `Failed to delete target ${id}.`);
155
- }
156
- return createResponse.success();
157
- };
158
-
159
- export interface PostTargetPayload {
160
- name: Target["name"];
161
- category: Target["category"];
162
- value: Target["value"];
163
- tagList: Target["tagList"];
164
- extra?: Target["extra"];
165
- details?: Target["details"];
166
- }
167
-
168
- class PostTargetPayloadValidator extends BaseValidator<PostTargetPayload> {
169
- protected requiredFields: (keyof PostTargetPayload)[] = ["category", "name", "value", "tagList"];
170
- protected optionalFields: (keyof PostTargetPayload)[] = ["extra", "details"];
171
-
172
- constructor() {
173
- super();
174
- // Add custom
175
- this.addValidator((val) => {
176
- return true;
177
- });
178
- }
179
- }
180
-
181
- export const postTarget = async (payload: PostTargetPayload) => {
182
- const validPayload = new PostTargetPayloadValidator().validate(payload);
183
-
184
- const { data, error } = await supabase.client
185
- .from("target")
186
- .insert([{ ...validPayload }])
187
- .select()
188
- .single();
189
-
190
- if (error) {
191
- handleSupabaseError("postTarget", error, "Failed to create target.");
192
- }
193
- return createResponse.success<Target>(data as Target);
194
- };
195
-
196
- export interface PatchTargetPayload extends PostTargetPayload {
197
- id: string;
198
- }
199
-
200
- export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) => {
201
- const { data: currentData, error: fetchError } = await supabase.client.from("target").select().eq("id", id).single();
202
- if (!currentData) {
203
- throw new Error("Target NOT exists");
204
- }
205
- if (fetchError) handleSupabaseError("patchTarget", fetchError, "Failed to fetch target.");
206
-
207
- const updatedTarget = new PostTargetPayloadValidator().validate(restPayload);
208
-
209
- const { data, error } = await supabase.client
210
- .from("target")
211
- .update({
212
- ...updatedTarget,
213
- })
214
- .eq("id", id)
215
- .select()
216
- .single();
217
-
218
- if (error) {
219
- handleSupabaseError("patchTarget", error, "Failed to update target.");
220
- }
221
- return createResponse.success<Target>(data as Target);
222
- };
223
-
224
- export const updateTargetDetails = async <T, D>({
225
- id,
226
- updateFn,
227
- beforeUpdateValidator = () => true,
228
- updateExtraFn,
229
- }: {
230
- id: string;
231
- updateFn: (existing: D) => D;
232
- beforeUpdateValidator?: (existing: D) => true | string;
233
- updateExtraFn?: (existing: D) => string;
234
- }) => {
235
- const { data: currentData, error: fetchError } = await supabase.client
236
- .from("target")
237
- .select("details")
238
- .eq("id", id)
239
- .single();
240
- if (!currentData) {
241
- const msg = "[updateTargetDetails] Target NOT exists: " + id;
242
- console.error(msg);
243
- throw new Error(msg);
244
- }
245
-
246
- const currentDetails = currentData.details as D;
247
-
248
- // Check before update
249
- const validatorResult = beforeUpdateValidator(currentDetails);
250
- if (validatorResult !== true) {
251
- throw new Error("[updateTargetDetails] validation failed. " + validatorResult);
252
- }
253
-
254
- const updatedDetails: D = updateFn(currentDetails);
255
- const updated =
256
- updateExtraFn == null
257
- ? {
258
- details: updatedDetails,
259
- }
260
- : {
261
- details: updatedDetails,
262
- extra: updateExtraFn(currentDetails),
263
- };
264
-
265
- const { data, error } = await supabase.client.from("target").update(updated).eq("id", id).select().single();
266
-
267
- if (error) handleSupabaseError("updateTargetDetails", error, "Failed to update target details.");
268
- return data as T;
269
- };
270
-
271
- export const createTarget = async <T extends Target, P extends object>({
272
- payload,
273
- validator,
274
- createFn,
275
- checkRedundancyFilterList,
276
- upsert = false,
277
- }: {
278
- payload: P;
279
- validator?: new () => BaseValidator<P>;
280
- createFn: (validPayload: P) => Omit<T, "id" | "created_at">;
281
- checkRedundancyFilterList?: QueryFilter[];
282
- upsert?: boolean;
283
- }) => {
284
- // (Optional)Step 1: validate payload
285
- const validPayload = validator != null ? new validator().validate(payload) : payload;
286
-
287
- // (Optional)Step 2: check redundancy
288
- if (checkRedundancyFilterList != null && checkRedundancyFilterList.length > 0) {
289
- const query = checkRedundancyFilterList.reduce((query, filter) => {
290
- switch (filter.operator) {
291
- case "eq":
292
- return query.eq(filter.field, filter.value);
293
- default:
294
- return query;
295
- }
296
- }, supabase.client.from("target").select("id"));
297
- const { data: existingTarget, error: existingError } = await query.maybeSingle();
298
- if (existingError) {
299
- handleSupabaseError("createTarget", existingError, "Failed to check target existence.");
300
- }
301
- if (existingTarget) {
302
- if (upsert) {
303
- const newTarget = createFn(validPayload);
304
- const updated = pick(newTarget, ["category", "name", "value", "tagList", "extra", "details"]);
305
- const { data, error } = await supabase.client
306
- .from("target")
307
- .update(updated)
308
- .eq("id", existingTarget.id)
309
- .select()
310
- .single();
311
- if (error) {
312
- handleSupabaseError("createTarget", error, "Failed to update existing target.");
313
- }
314
- return createResponse.success<T>(data);
315
- }
316
- const msg = "[createTarget] Target already exists";
317
- console.error(msg, checkRedundancyFilterList);
318
- throw new Error(msg);
319
- }
320
- }
321
-
322
- // Step 3: create target
323
- const newTarget = createFn(validPayload);
324
- const { data, error } = await supabase.client.from("target").insert([newTarget]).select().single();
325
- if (error) {
326
- handleSupabaseError("createTarget", error, "Failed to create target.");
327
- }
328
- return createResponse.success<T>(data);
329
- };
330
-
331
- export function validateWith<P extends object, V extends BaseValidator<P>>(ValidatorClass: new () => V) {
332
- return <R>(fn: (validPayload: P) => R) => {
333
- return (payload: P): R => {
334
- const validPayload = new ValidatorClass().validate(payload);
335
- return fn(validPayload);
336
- };
337
- };
338
- }
1
+ import { pick } from "lodash-es";
2
+ import { createResponse, Target } from "./core.interface";
3
+ import { supabase } from ".";
4
+ import { BaseValidator, handleSupabaseError } from "./core.utils";
5
+ import { PostgrestFilterBuilder } from "@supabase/postgrest-js";
6
+
7
+ export interface QueryFilter {
8
+ field: string;
9
+ operator: "eq";
10
+ value: unknown;
11
+ }
12
+
13
+ export interface QueryOrderBy {
14
+ field: string;
15
+ ascending: boolean;
16
+ }
17
+
18
+ export interface BaseQueryParams {
19
+ filterList?: QueryFilter[];
20
+ orderBy?: QueryOrderBy;
21
+ }
22
+
23
+ export const getTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
24
+ const query =
25
+ filterList != null && filterList.length > 0
26
+ ? filterList.reduce((query, filter) => {
27
+ switch (filter.operator) {
28
+ case "eq":
29
+ return query.eq(filter.field, filter.value);
30
+ default:
31
+ return query;
32
+ }
33
+ }, supabase.client.from("target").select().eq("id", id))
34
+ : supabase.client.from("target").select().eq("id", id);
35
+
36
+ const { data, error } = await query.single();
37
+ if (error) {
38
+ handleSupabaseError("getTarget", error, "Failed to fetch target.");
39
+ }
40
+ return createResponse.success<Target>(data as Target);
41
+ };
42
+
43
+ export const getPossibleTarget = async ({ filterList }: { filterList: QueryFilter[] }) => {
44
+ const query = filterList.reduce((query, filter) => {
45
+ switch (filter.operator) {
46
+ case "eq":
47
+ return query.eq(filter.field, filter.value);
48
+ default:
49
+ return query;
50
+ }
51
+ }, supabase.client.from("target").select());
52
+
53
+ const { data, error } = await query.maybeSingle();
54
+ if (error) {
55
+ handleSupabaseError("getPossibleTarget", error, "Failed to fetch target.");
56
+ }
57
+ return createResponse.success<Target | null>(data);
58
+ };
59
+
60
+ export interface GetTargetListParams extends BaseQueryParams {
61
+ pageNum: number;
62
+ pageSize: number;
63
+ category: Target["category"];
64
+ }
65
+
66
+ export const getTargetList = async ({
67
+ pageNum,
68
+ pageSize,
69
+ category,
70
+ filterList = [],
71
+ orderBy = { field: "created_at", ascending: false },
72
+ filterBuilder,
73
+ }: GetTargetListParams & {
74
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
75
+ filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
76
+ }) => {
77
+ const query =
78
+ filterBuilder ??
79
+ filterList.reduce((query, filter) => {
80
+ switch (filter.operator) {
81
+ case "eq":
82
+ return query.eq(filter.field, filter.value);
83
+ default:
84
+ return query;
85
+ }
86
+ }, supabase.client.from("target").select("*").eq("category", category));
87
+
88
+ const { data, error } = await query
89
+ .order(orderBy.field, { ascending: orderBy.ascending })
90
+ .range(pageNum * pageSize, (pageNum + 1) * pageSize - 1);
91
+
92
+ if (error) {
93
+ handleSupabaseError("getTargetList", error, "Failed to fetch target list.");
94
+ }
95
+ return createResponse.success<Target[]>(data);
96
+ };
97
+
98
+ export interface GetTargetTotalCountParams extends BaseQueryParams {
99
+ category: Target["category"];
100
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
101
+ filterBuilder?: PostgrestFilterBuilder<any, any, any[], "target", unknown>;
102
+ }
103
+
104
+ export const getTargetTotalCount = async ({
105
+ category,
106
+ filterList = [],
107
+ filterBuilder,
108
+ }: GetTargetTotalCountParams) => {
109
+ const query =
110
+ filterBuilder ??
111
+ filterList.reduce(
112
+ (query, filter) => {
113
+ switch (filter.operator) {
114
+ case "eq":
115
+ return query.eq(filter.field, filter.value);
116
+ default:
117
+ return query;
118
+ }
119
+ },
120
+ supabase.client
121
+ .from("target")
122
+ .select("id", { count: "exact", head: true }) // head: true means no row payload
123
+ .eq("category", category)
124
+ );
125
+
126
+ const { count, error } = await query;
127
+
128
+ if (error) {
129
+ handleSupabaseError("getTargetTotalCount", error, "Failed to fetch target count.");
130
+ }
131
+ return createResponse.success<number>(count ?? 0);
132
+ };
133
+
134
+ export const deleteTarget = async ({ id, filterList }: { id: string; filterList?: QueryFilter[] }) => {
135
+ // Check whether the target exists given filterList
136
+ if (filterList != null && filterList.length > 0) {
137
+ const query = filterList.reduce((query, filter) => {
138
+ switch (filter.operator) {
139
+ case "eq":
140
+ return query.eq(filter.field, filter.value);
141
+ default:
142
+ return query;
143
+ }
144
+ }, supabase.client.from("target").select().eq("id", id));
145
+ const { data: existingTarget, error: existingError } = await query.single();
146
+ if (existingError || existingTarget == null) {
147
+ const msg = `[deleteTarget] Cannot find the target ${id}`;
148
+ console.error(msg, { filterList }, existingError);
149
+ throw new Error(msg);
150
+ }
151
+ }
152
+ const { error } = await supabase.client.from("target").delete().eq("id", id);
153
+ if (error) {
154
+ handleSupabaseError("deleteTarget", error, `Failed to delete target ${id}.`);
155
+ }
156
+ return createResponse.success();
157
+ };
158
+
159
+ export interface PostTargetPayload {
160
+ name: Target["name"];
161
+ category: Target["category"];
162
+ value: Target["value"];
163
+ tagList: Target["tagList"];
164
+ extra?: Target["extra"];
165
+ details?: Target["details"];
166
+ }
167
+
168
+ class PostTargetPayloadValidator extends BaseValidator<PostTargetPayload> {
169
+ protected requiredFields: (keyof PostTargetPayload)[] = ["category", "name", "value", "tagList"];
170
+ protected optionalFields: (keyof PostTargetPayload)[] = ["extra", "details"];
171
+
172
+ constructor() {
173
+ super();
174
+ // Add custom
175
+ this.addValidator((val) => {
176
+ return true;
177
+ });
178
+ }
179
+ }
180
+
181
+ export const postTarget = async (payload: PostTargetPayload) => {
182
+ const validPayload = new PostTargetPayloadValidator().validate(payload);
183
+
184
+ const { data, error } = await supabase.client
185
+ .from("target")
186
+ .insert([{ ...validPayload }])
187
+ .select()
188
+ .single();
189
+
190
+ if (error) {
191
+ handleSupabaseError("postTarget", error, "Failed to create target.");
192
+ }
193
+ return createResponse.success<Target>(data as Target);
194
+ };
195
+
196
+ export interface PatchTargetPayload extends PostTargetPayload {
197
+ id: string;
198
+ }
199
+
200
+ export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) => {
201
+ const { data: currentData, error: fetchError } = await supabase.client.from("target").select().eq("id", id).single();
202
+ if (!currentData) {
203
+ throw new Error("Target NOT exists");
204
+ }
205
+ if (fetchError) handleSupabaseError("patchTarget", fetchError, "Failed to fetch target.");
206
+
207
+ const updatedTarget = new PostTargetPayloadValidator().validate(restPayload);
208
+
209
+ const { data, error } = await supabase.client
210
+ .from("target")
211
+ .update({
212
+ ...updatedTarget,
213
+ })
214
+ .eq("id", id)
215
+ .select()
216
+ .single();
217
+
218
+ if (error) {
219
+ handleSupabaseError("patchTarget", error, "Failed to update target.");
220
+ }
221
+ return createResponse.success<Target>(data as Target);
222
+ };
223
+
224
+ export const updateTargetDetails = async <T, D>({
225
+ id,
226
+ updateFn,
227
+ beforeUpdateValidator = () => true,
228
+ updateExtraFn,
229
+ }: {
230
+ id: string;
231
+ updateFn: (existing: D) => D;
232
+ beforeUpdateValidator?: (existing: D) => true | string;
233
+ updateExtraFn?: (existing: D) => string;
234
+ }) => {
235
+ const { data: currentData, error: fetchError } = await supabase.client
236
+ .from("target")
237
+ .select("details")
238
+ .eq("id", id)
239
+ .single();
240
+ if (!currentData) {
241
+ const msg = "[updateTargetDetails] Target NOT exists: " + id;
242
+ console.error(msg);
243
+ throw new Error(msg);
244
+ }
245
+
246
+ const currentDetails = currentData.details as D;
247
+
248
+ // Check before update
249
+ const validatorResult = beforeUpdateValidator(currentDetails);
250
+ if (validatorResult !== true) {
251
+ throw new Error("[updateTargetDetails] validation failed. " + validatorResult);
252
+ }
253
+
254
+ const updatedDetails: D = updateFn(currentDetails);
255
+ const updated =
256
+ updateExtraFn == null
257
+ ? {
258
+ details: updatedDetails,
259
+ }
260
+ : {
261
+ details: updatedDetails,
262
+ extra: updateExtraFn(currentDetails),
263
+ };
264
+
265
+ const { data, error } = await supabase.client.from("target").update(updated).eq("id", id).select().single();
266
+
267
+ if (error) handleSupabaseError("updateTargetDetails", error, "Failed to update target details.");
268
+ return data as T;
269
+ };
270
+
271
+ export const createTarget = async <T extends Target, P extends object>({
272
+ payload,
273
+ validator,
274
+ createFn,
275
+ checkRedundancyFilterList,
276
+ upsert = false,
277
+ }: {
278
+ payload: P;
279
+ validator?: new () => BaseValidator<P>;
280
+ createFn: (validPayload: P) => Omit<T, "id" | "created_at">;
281
+ checkRedundancyFilterList?: QueryFilter[];
282
+ upsert?: boolean;
283
+ }) => {
284
+ // (Optional)Step 1: validate payload
285
+ const validPayload = validator != null ? new validator().validate(payload) : payload;
286
+
287
+ // (Optional)Step 2: check redundancy
288
+ if (checkRedundancyFilterList != null && checkRedundancyFilterList.length > 0) {
289
+ const query = checkRedundancyFilterList.reduce((query, filter) => {
290
+ switch (filter.operator) {
291
+ case "eq":
292
+ return query.eq(filter.field, filter.value);
293
+ default:
294
+ return query;
295
+ }
296
+ }, supabase.client.from("target").select("id"));
297
+ const { data: existingTarget, error: existingError } = await query.maybeSingle();
298
+ if (existingError) {
299
+ handleSupabaseError("createTarget", existingError, "Failed to check target existence.");
300
+ }
301
+ if (existingTarget) {
302
+ if (upsert) {
303
+ const newTarget = createFn(validPayload);
304
+ const updated = pick(newTarget, ["category", "name", "value", "tagList", "extra", "details"]);
305
+ const { data, error } = await supabase.client
306
+ .from("target")
307
+ .update(updated)
308
+ .eq("id", existingTarget.id)
309
+ .select()
310
+ .single();
311
+ if (error) {
312
+ handleSupabaseError("createTarget", error, "Failed to update existing target.");
313
+ }
314
+ return createResponse.success<T>(data);
315
+ }
316
+ const msg = "[createTarget] Target already exists";
317
+ console.error(msg, checkRedundancyFilterList);
318
+ throw new Error(msg);
319
+ }
320
+ }
321
+
322
+ // Step 3: create target
323
+ const newTarget = createFn(validPayload);
324
+ const { data, error } = await supabase.client.from("target").insert([newTarget]).select().single();
325
+ if (error) {
326
+ handleSupabaseError("createTarget", error, "Failed to create target.");
327
+ }
328
+ return createResponse.success<T>(data);
329
+ };
330
+
331
+ export function validateWith<P extends object, V extends BaseValidator<P>>(ValidatorClass: new () => V) {
332
+ return <R>(fn: (validPayload: P) => R) => {
333
+ return (payload: P): R => {
334
+ const validPayload = new ValidatorClass().validate(payload);
335
+ return fn(validPayload);
336
+ };
337
+ };
338
+ }