@reactor-cloud/data 0.2.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/dist/index.js ADDED
@@ -0,0 +1,416 @@
1
+ import { encodeFilterValue, ok, request, post } from '@reactor-cloud/shared';
2
+
3
+ // src/client.ts
4
+ var PostgrestFilterBuilder = class {
5
+ table;
6
+ ctx;
7
+ selectColumns = "*";
8
+ filters = [];
9
+ orderClauses = [];
10
+ limitValue;
11
+ offsetValue;
12
+ countMode;
13
+ signalValue;
14
+ customHeaders = {};
15
+ responseFormat = "json";
16
+ explainMode;
17
+ resultModifier;
18
+ method = "GET";
19
+ body;
20
+ constructor(ctx, table) {
21
+ this.ctx = ctx;
22
+ this.table = table;
23
+ }
24
+ /** Equal to */
25
+ eq(column, value) {
26
+ this.filters.push({ column, operator: "eq", value, negated: false });
27
+ return this;
28
+ }
29
+ /** Not equal to */
30
+ neq(column, value) {
31
+ this.filters.push({ column, operator: "neq", value, negated: false });
32
+ return this;
33
+ }
34
+ /** Greater than */
35
+ gt(column, value) {
36
+ this.filters.push({ column, operator: "gt", value, negated: false });
37
+ return this;
38
+ }
39
+ /** Greater than or equal */
40
+ gte(column, value) {
41
+ this.filters.push({ column, operator: "gte", value, negated: false });
42
+ return this;
43
+ }
44
+ /** Less than */
45
+ lt(column, value) {
46
+ this.filters.push({ column, operator: "lt", value, negated: false });
47
+ return this;
48
+ }
49
+ /** Less than or equal */
50
+ lte(column, value) {
51
+ this.filters.push({ column, operator: "lte", value, negated: false });
52
+ return this;
53
+ }
54
+ /** Pattern match (LIKE) */
55
+ like(column, pattern) {
56
+ this.filters.push({ column, operator: "like", value: pattern, negated: false });
57
+ return this;
58
+ }
59
+ /** Case-insensitive pattern match (ILIKE) */
60
+ ilike(column, pattern) {
61
+ this.filters.push({ column, operator: "ilike", value: pattern, negated: false });
62
+ return this;
63
+ }
64
+ /** Is NULL or boolean */
65
+ is(column, value) {
66
+ this.filters.push({ column, operator: "is", value, negated: false });
67
+ return this;
68
+ }
69
+ /** In list */
70
+ in(column, values) {
71
+ this.filters.push({ column, operator: "in", value: values, negated: false });
72
+ return this;
73
+ }
74
+ /** Array contains */
75
+ contains(column, values) {
76
+ this.filters.push({ column, operator: "cs", value: values, negated: false });
77
+ return this;
78
+ }
79
+ /** Array contained by */
80
+ containedBy(column, values) {
81
+ this.filters.push({ column, operator: "cd", value: values, negated: false });
82
+ return this;
83
+ }
84
+ /** Array overlaps */
85
+ overlaps(column, values) {
86
+ this.filters.push({ column, operator: "ov", value: values, negated: false });
87
+ return this;
88
+ }
89
+ /** Full-text search */
90
+ textSearch(column, query, options) {
91
+ const { type = "plain", config } = options ?? {};
92
+ const value = config ? `${config}:${type}:${query}` : `${type}:${query}`;
93
+ this.filters.push({ column, operator: "fts", value, negated: false });
94
+ return this;
95
+ }
96
+ /** Match multiple conditions (shorthand for multiple eq) */
97
+ match(query) {
98
+ for (const [column, value] of Object.entries(query)) {
99
+ if (value !== void 0) {
100
+ this.filters.push({ column, operator: "eq", value, negated: false });
101
+ }
102
+ }
103
+ return this;
104
+ }
105
+ /** Negate a filter */
106
+ not(column, operator, value) {
107
+ this.filters.push({ column, operator, value, negated: true });
108
+ return this;
109
+ }
110
+ /** OR condition (raw string format) */
111
+ or(conditions, options) {
112
+ const column = options?.foreignTable ? `${options.foreignTable}.or` : "or";
113
+ this.filters.push({ column, operator: "eq", value: `(${conditions})`, negated: false });
114
+ return this;
115
+ }
116
+ /** Generic filter (escape hatch) */
117
+ filter(column, operator, value) {
118
+ this.filters.push({ column, operator, value, negated: false });
119
+ return this;
120
+ }
121
+ /** Order results */
122
+ order(column, options) {
123
+ const parts = [column];
124
+ if (options?.ascending === false) {
125
+ parts.push("desc");
126
+ }
127
+ if (options?.nullsFirst !== void 0) {
128
+ parts.push(options.nullsFirst ? "nullsfirst" : "nullslast");
129
+ }
130
+ if (options?.foreignTable) {
131
+ this.orderClauses.push(`${options.foreignTable}(${parts.join(".")})`);
132
+ } else {
133
+ this.orderClauses.push(parts.join("."));
134
+ }
135
+ return this;
136
+ }
137
+ /** Limit results */
138
+ limit(count, options) {
139
+ if (options?.foreignTable) {
140
+ this.customHeaders[`${options.foreignTable}-limit`] = String(count);
141
+ } else {
142
+ this.limitValue = count;
143
+ }
144
+ return this;
145
+ }
146
+ /** Offset results (for pagination) */
147
+ range(from, to, options) {
148
+ if (options?.foreignTable) {
149
+ this.customHeaders[`${options.foreignTable}-offset`] = String(from);
150
+ this.customHeaders[`${options.foreignTable}-limit`] = String(to - from + 1);
151
+ } else {
152
+ this.offsetValue = from;
153
+ this.limitValue = to - from + 1;
154
+ }
155
+ return this;
156
+ }
157
+ /** Provide an AbortSignal */
158
+ abortSignal(signal) {
159
+ this.signalValue = signal;
160
+ return this;
161
+ }
162
+ /** Return CSV instead of JSON */
163
+ csv() {
164
+ this.responseFormat = "csv";
165
+ return this;
166
+ }
167
+ /** Return query execution plan */
168
+ explain(options) {
169
+ this.explainMode = options ?? {};
170
+ return this;
171
+ }
172
+ /** Override return type */
173
+ returns() {
174
+ return this;
175
+ }
176
+ /** Execute and return exactly one row (throws if not exactly one) */
177
+ single() {
178
+ this.resultModifier = "single";
179
+ return this;
180
+ }
181
+ /** Execute and return zero or one row */
182
+ maybeSingle() {
183
+ this.resultModifier = "maybeSingle";
184
+ return this;
185
+ }
186
+ buildUrl() {
187
+ const url = new URL(`/data/v1/${encodeURIComponent(this.table)}`, this.ctx.baseUrl);
188
+ url.searchParams.set("select", this.selectColumns);
189
+ for (const filter of this.filters) {
190
+ const prefix = filter.negated ? "not." : "";
191
+ const value = filter.operator === "in" || filter.operator === "cs" || filter.operator === "cd" || filter.operator === "ov" ? encodeFilterValue(filter.value) : String(filter.value);
192
+ url.searchParams.append(filter.column, `${prefix}${filter.operator}.${value}`);
193
+ }
194
+ if (this.orderClauses.length > 0) {
195
+ url.searchParams.set("order", this.orderClauses.join(","));
196
+ }
197
+ if (this.limitValue !== void 0) {
198
+ url.searchParams.set("limit", String(this.limitValue));
199
+ }
200
+ if (this.offsetValue !== void 0) {
201
+ url.searchParams.set("offset", String(this.offsetValue));
202
+ }
203
+ return url.toString();
204
+ }
205
+ buildHeaders() {
206
+ const headers = { ...this.customHeaders };
207
+ if (this.countMode) {
208
+ headers["Prefer"] = `count=${this.countMode}`;
209
+ }
210
+ if (this.responseFormat === "csv") {
211
+ headers["Accept"] = "text/csv";
212
+ }
213
+ if (this.explainMode) {
214
+ if (this.explainMode.analyze) ;
215
+ if (this.explainMode.verbose) ;
216
+ if (this.explainMode.costs !== false) ;
217
+ if (this.explainMode.buffers) ;
218
+ headers["Accept"] = `application/vnd.pgrst.plan+${this.responseFormat === "csv" ? "text" : "json"}`;
219
+ }
220
+ if (this.resultModifier === "single" || this.resultModifier === "maybeSingle") {
221
+ headers["Accept"] = "application/vnd.pgrst.object+json";
222
+ }
223
+ return headers;
224
+ }
225
+ /** Execute the query */
226
+ async then(onfulfilled, _onrejected) {
227
+ const result = await this.execute();
228
+ if (this.resultModifier === "maybeSingle" && result.error && result.error.statusCode === 406) {
229
+ if (onfulfilled) {
230
+ return onfulfilled(ok(null));
231
+ }
232
+ return ok(null);
233
+ }
234
+ if (onfulfilled) {
235
+ return onfulfilled(result);
236
+ }
237
+ return result;
238
+ }
239
+ async execute() {
240
+ const url = this.buildUrl();
241
+ const headers = this.buildHeaders();
242
+ const result = await request(this.ctx, url, {
243
+ method: this.method,
244
+ body: this.body,
245
+ headers,
246
+ signal: this.signalValue,
247
+ responseType: this.responseFormat === "csv" ? "text" : "json"
248
+ });
249
+ return result;
250
+ }
251
+ /** Throw on error instead of returning { data, error } */
252
+ async throwOnError() {
253
+ const result = await this.execute();
254
+ if (this.resultModifier === "maybeSingle" && result.error && result.error.statusCode === 406) {
255
+ return null;
256
+ }
257
+ if (result.error) {
258
+ throw result.error;
259
+ }
260
+ return result.data;
261
+ }
262
+ };
263
+ var PostgrestQueryBuilder = class extends PostgrestFilterBuilder {
264
+ /** Select specific columns */
265
+ select(columns, options) {
266
+ this.selectColumns = columns ?? "*";
267
+ this.countMode = options?.count;
268
+ this.method = "GET";
269
+ return this;
270
+ }
271
+ /** Insert row(s) */
272
+ insert(values, options) {
273
+ this.method = "POST";
274
+ this.body = values;
275
+ this.countMode = options?.count;
276
+ return this;
277
+ }
278
+ /** Upsert row(s) */
279
+ upsert(values, options) {
280
+ this.method = "POST";
281
+ this.body = values;
282
+ this.countMode = options?.count;
283
+ this.customHeaders["Prefer"] = `resolution=${options?.ignoreDuplicates ? "ignore" : "merge"}-duplicates`;
284
+ if (options?.onConflict) {
285
+ this.customHeaders["Prefer"] += `,on_conflict=${options.onConflict}`;
286
+ }
287
+ return this;
288
+ }
289
+ /** Update row(s) */
290
+ update(values, options) {
291
+ this.method = "PATCH";
292
+ this.body = values;
293
+ this.countMode = options?.count;
294
+ return this;
295
+ }
296
+ /** Delete row(s) */
297
+ delete(options) {
298
+ this.method = "DELETE";
299
+ this.countMode = options?.count;
300
+ return this;
301
+ }
302
+ };
303
+ async function rpc(ctx, functionName, args, options) {
304
+ return post(
305
+ ctx,
306
+ `/data/v1/rpc/${encodeURIComponent(functionName)}`,
307
+ args,
308
+ {
309
+ signal: options?.signal,
310
+ headers: options?.headers
311
+ }
312
+ );
313
+ }
314
+ var RpcBuilder = class {
315
+ constructor(ctx, functionName) {
316
+ this.ctx = ctx;
317
+ this.functionName = functionName;
318
+ }
319
+ ctx;
320
+ functionName;
321
+ args;
322
+ signal;
323
+ customHeaders = {};
324
+ /** Set function arguments */
325
+ call(args) {
326
+ this.args = args;
327
+ return this;
328
+ }
329
+ /** Provide an AbortSignal */
330
+ abortSignal(signal) {
331
+ this.signal = signal;
332
+ return this;
333
+ }
334
+ /** Set custom headers */
335
+ headers(headers) {
336
+ this.customHeaders = { ...this.customHeaders, ...headers };
337
+ return this;
338
+ }
339
+ /** Execute the RPC call */
340
+ async then(onfulfilled, _onrejected) {
341
+ const result = await rpc(
342
+ this.ctx,
343
+ this.functionName,
344
+ this.args ?? {},
345
+ {
346
+ signal: this.signal,
347
+ headers: this.customHeaders
348
+ }
349
+ );
350
+ if (onfulfilled) {
351
+ return onfulfilled(result);
352
+ }
353
+ return result;
354
+ }
355
+ /** Throw on error */
356
+ async throwOnError() {
357
+ const result = await rpc(
358
+ this.ctx,
359
+ this.functionName,
360
+ this.args ?? {},
361
+ {
362
+ signal: this.signal,
363
+ headers: this.customHeaders
364
+ }
365
+ );
366
+ if (result.error) {
367
+ throw result.error;
368
+ }
369
+ return result.data;
370
+ }
371
+ };
372
+
373
+ // src/client.ts
374
+ var DataClient = class {
375
+ constructor(ctx) {
376
+ this.ctx = ctx;
377
+ }
378
+ ctx;
379
+ /**
380
+ * Start a query on a table.
381
+ *
382
+ * @param table - The table name
383
+ * @returns A query builder
384
+ */
385
+ from(table) {
386
+ return new PostgrestQueryBuilder(this.ctx, table);
387
+ }
388
+ /**
389
+ * Call a database function via RPC.
390
+ *
391
+ * @param functionName - The function name
392
+ * @param args - Function arguments
393
+ * @returns RPC builder
394
+ */
395
+ rpc(functionName, args) {
396
+ const builder = new RpcBuilder(this.ctx, functionName);
397
+ if (args) {
398
+ builder.call(args);
399
+ }
400
+ return builder;
401
+ }
402
+ /**
403
+ * Access a schema (for multi-schema support).
404
+ * Currently returns self as we only support public schema.
405
+ */
406
+ schema(_name) {
407
+ return this;
408
+ }
409
+ };
410
+ function createDataClient(ctx) {
411
+ return new DataClient(ctx);
412
+ }
413
+
414
+ export { DataClient, PostgrestFilterBuilder, PostgrestQueryBuilder, RpcBuilder, createDataClient, rpc };
415
+ //# sourceMappingURL=index.js.map
416
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/builder.ts","../src/rpc.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n type RequestContext,\n type Result,\n request,\n ok,\n encodeFilterValue,\n} from '@reactor-cloud/shared';\n\nimport type {\n CountMode,\n FilterOperator,\n FilterValue,\n OrderOptions,\n PendingFilter,\n TextSearchOptions,\n UpsertOptions,\n} from './types.js';\n\n/**\n * PostgrestFilterBuilder provides methods for building filter queries.\n */\nexport class PostgrestFilterBuilder<T, ResultType = T[]> {\n protected table: string;\n protected ctx: RequestContext;\n protected selectColumns: string = '*';\n protected filters: PendingFilter[] = [];\n protected orderClauses: string[] = [];\n protected limitValue?: number;\n protected offsetValue?: number;\n protected countMode?: CountMode;\n protected signalValue?: AbortSignal;\n protected customHeaders: Record<string, string> = {};\n protected responseFormat: 'json' | 'csv' = 'json';\n protected explainMode?: { analyze?: boolean; verbose?: boolean; costs?: boolean; buffers?: boolean };\n protected resultModifier?: 'single' | 'maybeSingle';\n protected method: 'GET' | 'POST' | 'PATCH' | 'DELETE' = 'GET';\n protected body?: unknown;\n\n constructor(ctx: RequestContext, table: string) {\n this.ctx = ctx;\n this.table = table;\n }\n\n /** Equal to */\n eq<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'eq', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Not equal to */\n neq<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'neq', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Greater than */\n gt<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'gt', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Greater than or equal */\n gte<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'gte', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Less than */\n lt<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'lt', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Less than or equal */\n lte<K extends keyof T & string>(column: K, value: T[K]): this {\n this.filters.push({ column, operator: 'lte', value: value as FilterValue, negated: false });\n return this;\n }\n\n /** Pattern match (LIKE) */\n like<K extends keyof T & string>(column: K, pattern: string): this {\n this.filters.push({ column, operator: 'like', value: pattern, negated: false });\n return this;\n }\n\n /** Case-insensitive pattern match (ILIKE) */\n ilike<K extends keyof T & string>(column: K, pattern: string): this {\n this.filters.push({ column, operator: 'ilike', value: pattern, negated: false });\n return this;\n }\n\n /** Is NULL or boolean */\n is<K extends keyof T & string>(column: K, value: null | boolean): this {\n this.filters.push({ column, operator: 'is', value, negated: false });\n return this;\n }\n\n /** In list */\n in<K extends keyof T & string>(column: K, values: T[K][]): this {\n this.filters.push({ column, operator: 'in', value: values as FilterValue, negated: false });\n return this;\n }\n\n /** Array contains */\n contains<K extends keyof T & string>(column: K, values: unknown[]): this {\n this.filters.push({ column, operator: 'cs', value: values as FilterValue, negated: false });\n return this;\n }\n\n /** Array contained by */\n containedBy<K extends keyof T & string>(column: K, values: unknown[]): this {\n this.filters.push({ column, operator: 'cd', value: values as FilterValue, negated: false });\n return this;\n }\n\n /** Array overlaps */\n overlaps<K extends keyof T & string>(column: K, values: unknown[]): this {\n this.filters.push({ column, operator: 'ov', value: values as FilterValue, negated: false });\n return this;\n }\n\n /** Full-text search */\n textSearch<K extends keyof T & string>(column: K, query: string, options?: TextSearchOptions): this {\n const { type = 'plain', config } = options ?? {};\n const value = config ? `${config}:${type}:${query}` : `${type}:${query}`;\n this.filters.push({ column, operator: 'fts', value, negated: false });\n return this;\n }\n\n /** Match multiple conditions (shorthand for multiple eq) */\n match(query: Partial<T>): this {\n for (const [column, value] of Object.entries(query)) {\n if (value !== undefined) {\n this.filters.push({ column, operator: 'eq', value: value as FilterValue, negated: false });\n }\n }\n return this;\n }\n\n /** Negate a filter */\n not<K extends keyof T & string>(column: K, operator: FilterOperator, value: FilterValue): this {\n this.filters.push({ column, operator, value, negated: true });\n return this;\n }\n\n /** OR condition (raw string format) */\n or(conditions: string, options?: { foreignTable?: string }): this {\n const column = options?.foreignTable ? `${options.foreignTable}.or` : 'or';\n this.filters.push({ column, operator: 'eq', value: `(${conditions})`, negated: false });\n return this;\n }\n\n /** Generic filter (escape hatch) */\n filter<K extends keyof T & string>(column: K, operator: FilterOperator, value: FilterValue): this {\n this.filters.push({ column, operator, value, negated: false });\n return this;\n }\n\n /** Order results */\n order<K extends keyof T & string>(column: K, options?: OrderOptions): this {\n const parts = [column as string];\n if (options?.ascending === false) {\n parts.push('desc');\n }\n if (options?.nullsFirst !== undefined) {\n parts.push(options.nullsFirst ? 'nullsfirst' : 'nullslast');\n }\n if (options?.foreignTable) {\n this.orderClauses.push(`${options.foreignTable}(${parts.join('.')})`);\n } else {\n this.orderClauses.push(parts.join('.'));\n }\n return this;\n }\n\n /** Limit results */\n limit(count: number, options?: { foreignTable?: string }): this {\n if (options?.foreignTable) {\n this.customHeaders[`${options.foreignTable}-limit`] = String(count);\n } else {\n this.limitValue = count;\n }\n return this;\n }\n\n /** Offset results (for pagination) */\n range(from: number, to: number, options?: { foreignTable?: string }): this {\n if (options?.foreignTable) {\n this.customHeaders[`${options.foreignTable}-offset`] = String(from);\n this.customHeaders[`${options.foreignTable}-limit`] = String(to - from + 1);\n } else {\n this.offsetValue = from;\n this.limitValue = to - from + 1;\n }\n return this;\n }\n\n /** Provide an AbortSignal */\n abortSignal(signal: AbortSignal): this {\n this.signalValue = signal;\n return this;\n }\n\n /** Return CSV instead of JSON */\n csv(): PostgrestFilterBuilder<T, string> {\n this.responseFormat = 'csv';\n return this as unknown as PostgrestFilterBuilder<T, string>;\n }\n\n /** Return query execution plan */\n explain(options?: { analyze?: boolean; verbose?: boolean; costs?: boolean; buffers?: boolean }): this {\n this.explainMode = options ?? {};\n return this;\n }\n\n /** Override return type */\n returns<R>(): PostgrestFilterBuilder<R, R[]> {\n return this as unknown as PostgrestFilterBuilder<R, R[]>;\n }\n\n /** Execute and return exactly one row (throws if not exactly one) */\n single(): PostgrestFilterBuilder<T, T> {\n this.resultModifier = 'single';\n return this as unknown as PostgrestFilterBuilder<T, T>;\n }\n\n /** Execute and return zero or one row */\n maybeSingle(): PostgrestFilterBuilder<T, T | null> {\n this.resultModifier = 'maybeSingle';\n return this as unknown as PostgrestFilterBuilder<T, T | null>;\n }\n\n protected buildUrl(): string {\n const url = new URL(`/data/v1/${encodeURIComponent(this.table)}`, this.ctx.baseUrl);\n\n // Select\n url.searchParams.set('select', this.selectColumns);\n\n // Filters\n for (const filter of this.filters) {\n const prefix = filter.negated ? 'not.' : '';\n const value = filter.operator === 'in' || filter.operator === 'cs' || filter.operator === 'cd' || filter.operator === 'ov'\n ? encodeFilterValue(filter.value)\n : String(filter.value);\n url.searchParams.append(filter.column, `${prefix}${filter.operator}.${value}`);\n }\n\n // Order\n if (this.orderClauses.length > 0) {\n url.searchParams.set('order', this.orderClauses.join(','));\n }\n\n // Pagination\n if (this.limitValue !== undefined) {\n url.searchParams.set('limit', String(this.limitValue));\n }\n if (this.offsetValue !== undefined) {\n url.searchParams.set('offset', String(this.offsetValue));\n }\n\n return url.toString();\n }\n\n protected buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = { ...this.customHeaders };\n\n if (this.countMode) {\n headers['Prefer'] = `count=${this.countMode}`;\n }\n\n if (this.responseFormat === 'csv') {\n headers['Accept'] = 'text/csv';\n }\n\n if (this.explainMode) {\n const parts = ['explain'];\n if (this.explainMode.analyze) parts.push('analyze');\n if (this.explainMode.verbose) parts.push('verbose');\n if (this.explainMode.costs !== false) parts.push('costs');\n if (this.explainMode.buffers) parts.push('buffers');\n headers['Accept'] = `application/vnd.pgrst.plan+${this.responseFormat === 'csv' ? 'text' : 'json'}`;\n }\n\n if (this.resultModifier === 'single' || this.resultModifier === 'maybeSingle') {\n headers['Accept'] = 'application/vnd.pgrst.object+json';\n }\n\n return headers;\n }\n\n /** Execute the query */\n async then<TResult1 = Result<ResultType>, TResult2 = never>(\n onfulfilled?: ((value: Result<ResultType>) => TResult1 | PromiseLike<TResult1>) | null,\n _onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n const result = await this.execute();\n\n // Handle maybeSingle - empty result should return null, not error\n if (this.resultModifier === 'maybeSingle' && result.error && result.error.statusCode === 406) {\n if (onfulfilled) {\n return onfulfilled(ok(null) as Result<ResultType>);\n }\n return ok(null) as unknown as TResult1;\n }\n\n if (onfulfilled) {\n return onfulfilled(result as Result<ResultType>);\n }\n return result as unknown as TResult1;\n }\n\n protected async execute(): Promise<Result<unknown>> {\n const url = this.buildUrl();\n const headers = this.buildHeaders();\n\n const result = await request<unknown>(this.ctx, url, {\n method: this.method,\n body: this.body,\n headers,\n signal: this.signalValue,\n responseType: this.responseFormat === 'csv' ? 'text' : 'json',\n });\n\n return result;\n }\n\n /** Throw on error instead of returning { data, error } */\n async throwOnError(): Promise<ResultType> {\n const result = await this.execute();\n\n // Handle maybeSingle - empty result should return null, not throw\n if (this.resultModifier === 'maybeSingle' && result.error && result.error.statusCode === 406) {\n return null as ResultType;\n }\n\n if (result.error) {\n throw result.error;\n }\n return result.data as ResultType;\n }\n}\n\n/**\n * Builder for SELECT queries with column selection.\n */\nexport class PostgrestQueryBuilder<T> extends PostgrestFilterBuilder<T> {\n /** Select specific columns */\n select<Columns extends string = '*'>(\n columns?: Columns,\n options?: { count?: CountMode }\n ): PostgrestFilterBuilder<T> {\n this.selectColumns = columns ?? '*';\n this.countMode = options?.count;\n this.method = 'GET';\n return this;\n }\n\n /** Insert row(s) */\n insert(values: Partial<T> | Partial<T>[], options?: { count?: CountMode }): PostgrestFilterBuilder<T> {\n this.method = 'POST';\n this.body = values;\n this.countMode = options?.count;\n return this;\n }\n\n /** Upsert row(s) */\n upsert(values: Partial<T> | Partial<T>[], options?: UpsertOptions): PostgrestFilterBuilder<T> {\n this.method = 'POST';\n this.body = values;\n this.countMode = options?.count;\n this.customHeaders['Prefer'] = `resolution=${options?.ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`;\n if (options?.onConflict) {\n this.customHeaders['Prefer'] += `,on_conflict=${options.onConflict}`;\n }\n return this;\n }\n\n /** Update row(s) */\n update(values: Partial<T>, options?: { count?: CountMode }): PostgrestFilterBuilder<T> {\n this.method = 'PATCH';\n this.body = values;\n this.countMode = options?.count;\n return this;\n }\n\n /** Delete row(s) */\n delete(options?: { count?: CountMode }): PostgrestFilterBuilder<T> {\n this.method = 'DELETE';\n this.countMode = options?.count;\n return this;\n }\n}\n","import {\n type RequestContext,\n type Result,\n post,\n} from '@reactor-cloud/shared';\n\n/**\n * Call a database function via RPC.\n */\nexport async function rpc<Args extends Record<string, unknown>, Returns>(\n ctx: RequestContext,\n functionName: string,\n args: Args,\n options?: { signal?: AbortSignal; headers?: Record<string, string> }\n): Promise<Result<Returns>> {\n return post<Returns>(\n ctx,\n `/data/v1/rpc/${encodeURIComponent(functionName)}`,\n args,\n {\n signal: options?.signal,\n headers: options?.headers,\n }\n );\n}\n\n/**\n * RPC builder for type-safe function calls.\n */\nexport class RpcBuilder<Args extends Record<string, unknown>, Returns> {\n private args?: Args;\n private signal?: AbortSignal;\n private customHeaders: Record<string, string> = {};\n\n constructor(\n private ctx: RequestContext,\n private functionName: string\n ) {}\n\n /** Set function arguments */\n call(args: Args): this {\n this.args = args;\n return this;\n }\n\n /** Provide an AbortSignal */\n abortSignal(signal: AbortSignal): this {\n this.signal = signal;\n return this;\n }\n\n /** Set custom headers */\n headers(headers: Record<string, string>): this {\n this.customHeaders = { ...this.customHeaders, ...headers };\n return this;\n }\n\n /** Execute the RPC call */\n async then<TResult1 = Result<Returns>, TResult2 = never>(\n onfulfilled?: ((value: Result<Returns>) => TResult1 | PromiseLike<TResult1>) | null,\n _onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n const result = await rpc<Args, Returns>(\n this.ctx,\n this.functionName,\n this.args ?? ({} as Args),\n {\n signal: this.signal,\n headers: this.customHeaders,\n }\n );\n\n if (onfulfilled) {\n return onfulfilled(result);\n }\n return result as unknown as TResult1;\n }\n\n /** Throw on error */\n async throwOnError(): Promise<Returns> {\n const result = await rpc<Args, Returns>(\n this.ctx,\n this.functionName,\n this.args ?? ({} as Args),\n {\n signal: this.signal,\n headers: this.customHeaders,\n }\n );\n\n if (result.error) {\n throw result.error;\n }\n return result.data;\n }\n}\n","import { type RequestContext } from '@reactor-cloud/shared';\nimport { PostgrestQueryBuilder } from './builder.js';\nimport { RpcBuilder } from './rpc.js';\nimport type { GenericSchema } from './types.js';\n\n/**\n * Data client for Reactor - PostgREST-style query builder.\n *\n * @example\n * ```ts\n * const client = new DataClient(ctx);\n *\n * // Select with filters\n * const { data, error } = await client\n * .from('posts')\n * .select('id, title, author:users(name)')\n * .eq('published', true)\n * .order('created_at', { ascending: false })\n * .limit(10);\n *\n * // Insert\n * const { data } = await client\n * .from('posts')\n * .insert({ title: 'Hello', body: 'World' })\n * .select()\n * .single();\n *\n * // RPC\n * const { data } = await client.rpc('search', { query: 'rust' });\n * ```\n */\nexport class DataClient<Schema extends GenericSchema = GenericSchema> {\n constructor(private ctx: RequestContext) {}\n\n /**\n * Start a query on a table.\n *\n * @param table - The table name\n * @returns A query builder\n */\n from<TableName extends keyof Schema['Tables'] & string>(\n table: TableName\n ): PostgrestQueryBuilder<Schema['Tables'][TableName]['Row']> {\n return new PostgrestQueryBuilder(this.ctx, table);\n }\n\n /**\n * Call a database function via RPC.\n *\n * @param functionName - The function name\n * @param args - Function arguments\n * @returns RPC builder\n */\n rpc<\n FunctionName extends keyof Schema['Functions'] & string,\n Args extends Schema['Functions'][FunctionName]['Args'],\n Returns = Schema['Functions'][FunctionName]['Returns']\n >(\n functionName: FunctionName,\n args?: Args\n ): RpcBuilder<Args, Returns> {\n const builder = new RpcBuilder<Args, Returns>(this.ctx, functionName);\n if (args) {\n builder.call(args);\n }\n return builder;\n }\n\n /**\n * Access a schema (for multi-schema support).\n * Currently returns self as we only support public schema.\n */\n schema(_name: string): DataClient<Schema> {\n // TODO: Implement multi-schema support when needed\n return this;\n }\n}\n\n/**\n * Create a data client with typed schema.\n */\nexport function createDataClient<Schema extends GenericSchema = GenericSchema>(\n ctx: RequestContext\n): DataClient<Schema> {\n return new DataClient<Schema>(ctx);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@reactor-cloud/data",
3
+ "version": "0.2.0",
4
+ "description": "Data client for Reactor JS SDK - PostgREST-style query builder",
5
+ "keywords": [
6
+ "reactor",
7
+ "sdk",
8
+ "data",
9
+ "postgrest",
10
+ "database"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Reactor Team",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/reactor/reactor.git",
17
+ "directory": "sdks/js/packages/data"
18
+ },
19
+ "type": "module",
20
+ "sideEffects": false,
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/index.d.cts",
29
+ "default": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "dependencies": {
41
+ "@reactor-cloud/shared": "0.2.0"
42
+ },
43
+ "devDependencies": {
44
+ "typescript": "^5.7.2",
45
+ "vitest": "^2.1.8"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "typecheck": "tsc --noEmit",
53
+ "lint": "echo 'no linter configured'",
54
+ "clean": "rm -rf dist coverage .turbo"
55
+ }
56
+ }