@isomorph.ai/app-sdk 1.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.
- package/dist/index.d.ts +367 -0
- package/dist/index.js +602 -0
- package/dist/integration-execute-input-rules.d.ts +187 -0
- package/dist/integration-execute-input-rules.js +114 -0
- package/dist/supabase-compat.d.ts +323 -0
- package/dist/supabase-compat.js +308 -0
- package/dist/supabase-compat.typecheck.d.ts +23 -0
- package/dist/supabase-compat.typecheck.js +92 -0
- package/package.json +15 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* supabase-js compatibility layer for the Isomorph App SDK.
|
|
3
|
+
*
|
|
4
|
+
* Converted Supabase apps swap `createClient` from '@supabase/supabase-js' for
|
|
5
|
+
* `createSupabaseCompatClient(createClient())` and keep call sites unchanged.
|
|
6
|
+
* Everything here maps onto the existing Isomorph client surfaces only; nothing
|
|
7
|
+
* new is invented on the wire.
|
|
8
|
+
*
|
|
9
|
+
* Contract differences this bridges (all found in converted-app reviews):
|
|
10
|
+
* - supabase call sites destructure `{ data, error }` and expect NEVER to see a
|
|
11
|
+
* throw; the Isomorph SDK returns values and throws IsomorphError. Every async
|
|
12
|
+
* method here catches and returns a PostgrestError-shaped `error` instead.
|
|
13
|
+
* - `functions.invoke(name, { body })` must UNWRAP the `body` option before it
|
|
14
|
+
* reaches the action server (supabase-js does; a passthrough left payloads
|
|
15
|
+
* wrapped in `{ body: ... }`).
|
|
16
|
+
* - storage is bucket-scoped in supabase and path-scoped in Isomorph; object
|
|
17
|
+
* keys are mapped as `<bucket>/<path>` so distinct buckets stay distinct.
|
|
18
|
+
*/
|
|
19
|
+
import { IsomorphError, } from './index.js';
|
|
20
|
+
/**
|
|
21
|
+
* Decode a selected result without asserting a relation shape the SDK cannot
|
|
22
|
+
* infer from a select string. The decoder should check the extra fields and
|
|
23
|
+
* return the desired shape; a thrown validation error becomes a compat error.
|
|
24
|
+
* Existing query errors bypass the decoder, and successful counts are retained.
|
|
25
|
+
*/
|
|
26
|
+
export function decodeSupabaseCompatResponse(response, decode) {
|
|
27
|
+
if (response.error)
|
|
28
|
+
return response;
|
|
29
|
+
try {
|
|
30
|
+
return { data: decode(response.data), error: null, count: response.count };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return { data: null, error: toCompatError(error), count: null };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function toCompatError(error) {
|
|
37
|
+
if (error instanceof IsomorphError) {
|
|
38
|
+
return { message: error.message, code: error.category, details: error.details };
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof Error)
|
|
41
|
+
return { message: error.message };
|
|
42
|
+
return { message: String(error) };
|
|
43
|
+
}
|
|
44
|
+
function notSupported(method) {
|
|
45
|
+
return { message: `not supported on Isomorph: ${method}` };
|
|
46
|
+
}
|
|
47
|
+
export class SupabaseCompatQueryBuilder {
|
|
48
|
+
inner;
|
|
49
|
+
head;
|
|
50
|
+
constructor(inner, ...head) {
|
|
51
|
+
this.inner = inner;
|
|
52
|
+
// Omitting the flag is allowed only for the default Head=false builder.
|
|
53
|
+
this.head = (head[0] ?? false);
|
|
54
|
+
}
|
|
55
|
+
chain(next) {
|
|
56
|
+
return new SupabaseCompatQueryBuilder(next, this.head);
|
|
57
|
+
}
|
|
58
|
+
select(columns, options) {
|
|
59
|
+
return new SupabaseCompatQueryBuilder(this.inner.select(columns, options), options?.head ?? false);
|
|
60
|
+
}
|
|
61
|
+
insert(values) {
|
|
62
|
+
return this.chain(this.inner.insert(values));
|
|
63
|
+
}
|
|
64
|
+
update(values) {
|
|
65
|
+
return this.chain(this.inner.update(values));
|
|
66
|
+
}
|
|
67
|
+
/** Optionless `upsert(row)` must pass through untouched (supabase allows it). */
|
|
68
|
+
upsert(values, options) {
|
|
69
|
+
return this.chain(this.inner.upsert(values, options));
|
|
70
|
+
}
|
|
71
|
+
delete() { return this.chain(this.inner.delete()); }
|
|
72
|
+
eq(column, value) { return this.chain(this.inner.eq(column, value)); }
|
|
73
|
+
neq(column, value) { return this.chain(this.inner.neq(column, value)); }
|
|
74
|
+
gt(column, value) { return this.chain(this.inner.gt(column, value)); }
|
|
75
|
+
gte(column, value) { return this.chain(this.inner.gte(column, value)); }
|
|
76
|
+
lt(column, value) { return this.chain(this.inner.lt(column, value)); }
|
|
77
|
+
lte(column, value) { return this.chain(this.inner.lte(column, value)); }
|
|
78
|
+
is(column, value) { return this.chain(this.inner.is(column, value)); }
|
|
79
|
+
not(column, operator, value) {
|
|
80
|
+
return this.chain(this.inner.not(column, operator, value));
|
|
81
|
+
}
|
|
82
|
+
in(column, values) { return this.chain(this.inner.in(column, values)); }
|
|
83
|
+
ilike(column, value) { return this.chain(this.inner.ilike(column, value)); }
|
|
84
|
+
or(expression) { return this.chain(this.inner.or(expression)); }
|
|
85
|
+
order(column, options) {
|
|
86
|
+
return this.chain(this.inner.order(column, options));
|
|
87
|
+
}
|
|
88
|
+
limit(value) { return this.chain(this.inner.limit(value)); }
|
|
89
|
+
range(from, to) { return this.chain(this.inner.range(from, to)); }
|
|
90
|
+
single() {
|
|
91
|
+
return new SupabaseCompatQueryBuilder(this.inner.single(), this.head);
|
|
92
|
+
}
|
|
93
|
+
maybeSingle() {
|
|
94
|
+
return new SupabaseCompatQueryBuilder(this.inner.maybeSingle(), this.head);
|
|
95
|
+
}
|
|
96
|
+
async resolve() {
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.inner.execute();
|
|
99
|
+
// Head carries the same flag sent to the gateway. TypeScript cannot
|
|
100
|
+
// narrow a generic boolean, so record just this conditional here.
|
|
101
|
+
const data = (this.head ? null : result.data);
|
|
102
|
+
return { data, error: null, count: result.count ?? null };
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return { data: null, error: toCompatError(error), count: null };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
then(onfulfilled, onrejected) {
|
|
109
|
+
return this.resolve().then(onfulfilled, onrejected);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function toCompatUser(user) {
|
|
113
|
+
const metadata = typeof user.appUser === 'object' && user.appUser !== null
|
|
114
|
+
? user.appUser
|
|
115
|
+
: {};
|
|
116
|
+
return { id: user.id, email: user.email, app_metadata: {}, user_metadata: metadata };
|
|
117
|
+
}
|
|
118
|
+
function toCompatSession(user) {
|
|
119
|
+
return { user: toCompatUser(user), access_token: 'isomorph-gateway-cookie', token_type: 'bearer' };
|
|
120
|
+
}
|
|
121
|
+
/** Supabase buckets map to Isomorph path prefixes: object key = `<bucket>/<path>`. */
|
|
122
|
+
function objectPath(bucket, path) {
|
|
123
|
+
return `${bucket.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
124
|
+
}
|
|
125
|
+
function toBlob(body, contentType) {
|
|
126
|
+
if (body instanceof Blob)
|
|
127
|
+
return body;
|
|
128
|
+
return new Blob([body], { type: contentType ?? 'application/octet-stream' });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build a supabase-js-shaped client on top of an existing Isomorph client.
|
|
132
|
+
* Pass the object returned by the SDK's `createClient`.
|
|
133
|
+
*/
|
|
134
|
+
export function createSupabaseCompatClient(isomorph) {
|
|
135
|
+
return {
|
|
136
|
+
from(table) {
|
|
137
|
+
return new SupabaseCompatQueryBuilder(isomorph.data.from(table));
|
|
138
|
+
},
|
|
139
|
+
rpc: async (name, params = {}) => {
|
|
140
|
+
try {
|
|
141
|
+
// The gateway returns a scalar function's result as PostgREST-style
|
|
142
|
+
// rows ([{fn_name: value}]); supabase-js rpc() resolves the bare
|
|
143
|
+
// value. Unwrap that shape so untouched call sites destructure
|
|
144
|
+
// correctly (a winner's user.id came back undefined without this).
|
|
145
|
+
const raw = await isomorph.data.rpc(name, params);
|
|
146
|
+
let data = raw;
|
|
147
|
+
if (Array.isArray(raw) && raw.length === 1 && raw[0] !== null && typeof raw[0] === 'object') {
|
|
148
|
+
const row = raw[0];
|
|
149
|
+
const keys = Object.keys(row);
|
|
150
|
+
if (keys.length === 1 && keys[0] === name) {
|
|
151
|
+
data = row[name];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { data: data, error: null };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return { data: null, error: toCompatError(error) };
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
auth: {
|
|
161
|
+
getSession: async () => {
|
|
162
|
+
try {
|
|
163
|
+
const user = await isomorph.identity.current();
|
|
164
|
+
return { data: { session: user ? toCompatSession(user) : null }, error: null };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return { data: { session: null }, error: toCompatError(error) };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
getUser: async () => {
|
|
171
|
+
try {
|
|
172
|
+
const user = await isomorph.identity.current();
|
|
173
|
+
// Signed-out is data.user = null with error = null (kinder than
|
|
174
|
+
// supabase's AuthSessionMissingError for `if (user)` call sites).
|
|
175
|
+
return { data: { user: user ? toCompatUser(user) : null }, error: null };
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return { data: { user: null }, error: toCompatError(error) };
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
onAuthStateChange: (callback) => {
|
|
182
|
+
const unsubscribe = isomorph.identity.onChange((user) => {
|
|
183
|
+
if (user)
|
|
184
|
+
callback('SIGNED_IN', toCompatSession(user));
|
|
185
|
+
else
|
|
186
|
+
callback('SIGNED_OUT', null);
|
|
187
|
+
});
|
|
188
|
+
return { data: { subscription: { unsubscribe } } };
|
|
189
|
+
},
|
|
190
|
+
/**
|
|
191
|
+
* The Isomorph edge gateway owns sign-out: this clears local identity and
|
|
192
|
+
* redirects the browser through /__harbour/logout (via identity.signOut).
|
|
193
|
+
*/
|
|
194
|
+
signOut: async () => {
|
|
195
|
+
isomorph.identity.signOut();
|
|
196
|
+
return { error: null };
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
functions: {
|
|
200
|
+
invoke: async (name, options) => {
|
|
201
|
+
try {
|
|
202
|
+
if (options?.method !== undefined && options.method !== 'POST') {
|
|
203
|
+
return { data: null, error: notSupported(`functions.invoke method ${options.method}`) };
|
|
204
|
+
}
|
|
205
|
+
if (options?.headers && Object.keys(options.headers).length > 0) {
|
|
206
|
+
return { data: null, error: notSupported('functions.invoke custom headers') };
|
|
207
|
+
}
|
|
208
|
+
// supabase-js unwraps the `body` option before sending; a converted
|
|
209
|
+
// app's action server must receive the payload, not `{ body: ... }`.
|
|
210
|
+
return { data: await isomorph.actions.invoke(name, options?.body ?? {}), error: null };
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return { data: null, error: toCompatError(error) };
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
storage: {
|
|
218
|
+
from: (bucket) => ({
|
|
219
|
+
upload: async (path, body, options) => {
|
|
220
|
+
try {
|
|
221
|
+
const fullPath = objectPath(bucket, path);
|
|
222
|
+
await isomorph.files.upload(fullPath, toBlob(body, options?.contentType));
|
|
223
|
+
return { data: { path, fullPath }, error: null };
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
return { data: null, error: toCompatError(error) };
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
/** Mapped through createSignedUrl + a plain fetch of the signed URL. */
|
|
230
|
+
download: async (path) => {
|
|
231
|
+
try {
|
|
232
|
+
const { signedUrl } = await isomorph.files.createSignedUrl(objectPath(bucket, path));
|
|
233
|
+
const response = await globalThis.fetch(signedUrl);
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
return { data: null, error: { message: `download failed with ${response.status}` } };
|
|
236
|
+
}
|
|
237
|
+
return { data: await response.blob(), error: null };
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return { data: null, error: toCompatError(error) };
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
remove: async (paths) => {
|
|
244
|
+
try {
|
|
245
|
+
await isomorph.files.remove(paths.map((path) => objectPath(bucket, path)));
|
|
246
|
+
return { data: paths.map((name) => ({ name })), error: null };
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return { data: null, error: toCompatError(error) };
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
list: async (prefix = '') => {
|
|
253
|
+
try {
|
|
254
|
+
return { data: await isomorph.files.list(objectPath(bucket, prefix)), error: null };
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return { data: null, error: toCompatError(error) };
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
/** Sync, like supabase-js: URL construction only, no network call. */
|
|
261
|
+
getPublicUrl: (path) => ({
|
|
262
|
+
data: { publicUrl: isomorph.files.getPublicUrl(objectPath(bucket, path)) },
|
|
263
|
+
}),
|
|
264
|
+
createSignedUrl: async (path, expiresIn) => {
|
|
265
|
+
try {
|
|
266
|
+
const { signedUrl } = await isomorph.files.createSignedUrl(objectPath(bucket, path), expiresIn);
|
|
267
|
+
return { data: { signedUrl }, error: null };
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return { data: null, error: toCompatError(error) };
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
createSignedUrls: async (paths, expiresIn) => {
|
|
274
|
+
try {
|
|
275
|
+
const signed = await isomorph.files.createSignedUrls(paths.map((path) => objectPath(bucket, path)), expiresIn);
|
|
276
|
+
return { data: signed, error: null };
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
return { data: null, error: toCompatError(error) };
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
/** No Isomorph files move; declared so call sites fail loudly, not with a TypeError. */
|
|
283
|
+
move: async (fromPath, toPath) => {
|
|
284
|
+
void fromPath;
|
|
285
|
+
void toPath;
|
|
286
|
+
return { data: null, error: notSupported('storage.move') };
|
|
287
|
+
},
|
|
288
|
+
/** No Isomorph files copy; declared so call sites fail loudly, not with a TypeError. */
|
|
289
|
+
copy: async (fromPath, toPath) => {
|
|
290
|
+
void fromPath;
|
|
291
|
+
void toPath;
|
|
292
|
+
return { data: null, error: notSupported('storage.copy') };
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
},
|
|
296
|
+
/** Isomorph's channel builder is already supabase-shaped; pure passthrough. */
|
|
297
|
+
channel: (name) => isomorph.realtime.channel(name),
|
|
298
|
+
removeChannel: (subscription) => {
|
|
299
|
+
try {
|
|
300
|
+
subscription.unsubscribe();
|
|
301
|
+
return Promise.resolve('ok');
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return Promise.resolve('error');
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare function typedRows(): Promise<string | null>;
|
|
2
|
+
declare function untypedDefault(): Promise<unknown>;
|
|
3
|
+
declare function singleAfterErrorGuard(): Promise<string>;
|
|
4
|
+
declare function maybeSingleStillAllowsNoRow(): Promise<string | undefined>;
|
|
5
|
+
declare function headCount(options?: {
|
|
6
|
+
count?: 'exact';
|
|
7
|
+
head?: boolean;
|
|
8
|
+
}): Promise<number | null>;
|
|
9
|
+
type LineTable = 'cost_sheet_materials' | 'cost_sheet_labour' | 'cost_sheet_overhead';
|
|
10
|
+
declare function knownTableUnion(table: LineTable): Promise<string | undefined>;
|
|
11
|
+
declare function unknownTableRemainsPermissive(table: string): Promise<unknown>;
|
|
12
|
+
declare function checkedNestedSelection(): Promise<string | null>;
|
|
13
|
+
export declare const _compatTypingChecks: {
|
|
14
|
+
typedRows: typeof typedRows;
|
|
15
|
+
untypedDefault: typeof untypedDefault;
|
|
16
|
+
singleAfterErrorGuard: typeof singleAfterErrorGuard;
|
|
17
|
+
maybeSingleStillAllowsNoRow: typeof maybeSingleStillAllowsNoRow;
|
|
18
|
+
headCount: typeof headCount;
|
|
19
|
+
knownTableUnion: typeof knownTableUnion;
|
|
20
|
+
unknownTableRemainsPermissive: typeof unknownTableRemainsPermissive;
|
|
21
|
+
checkedNestedSelection: typeof checkedNestedSelection;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Compile-time assertions for the compat client's Database typing. This file
|
|
2
|
+
// ships no runtime code; `tsc -p` (npm test) is what checks it.
|
|
3
|
+
import { createSupabaseCompatClient, decodeSupabaseCompatResponse } from './index.js';
|
|
4
|
+
async function typedRows() {
|
|
5
|
+
const supabase = createSupabaseCompatClient(source);
|
|
6
|
+
const { data } = await supabase.from('approval_steps').select('*');
|
|
7
|
+
const step = data?.[0];
|
|
8
|
+
// approver_email is a typed column, not unknown.
|
|
9
|
+
return step ? step.approver_email : null;
|
|
10
|
+
}
|
|
11
|
+
async function untypedDefault() {
|
|
12
|
+
const supabase = createSupabaseCompatClient(source);
|
|
13
|
+
const { data } = await supabase.from('anything').select('*');
|
|
14
|
+
// Without a Database type every row stays Record<string, unknown>.
|
|
15
|
+
const value = data?.[0]?.whatever;
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
async function singleAfterErrorGuard() {
|
|
19
|
+
const supabase = createSupabaseCompatClient(source);
|
|
20
|
+
const { data, error } = await supabase.from('approval_steps').insert({}).select().single();
|
|
21
|
+
if (error)
|
|
22
|
+
throw error;
|
|
23
|
+
return data.id;
|
|
24
|
+
}
|
|
25
|
+
async function maybeSingleStillAllowsNoRow() {
|
|
26
|
+
const supabase = createSupabaseCompatClient(source);
|
|
27
|
+
const { data, error } = await supabase.from('approval_steps').select().maybeSingle();
|
|
28
|
+
if (error)
|
|
29
|
+
throw error;
|
|
30
|
+
// @ts-expect-error maybeSingle can succeed without a row.
|
|
31
|
+
const required = data;
|
|
32
|
+
return data?.id;
|
|
33
|
+
}
|
|
34
|
+
async function headCount(options) {
|
|
35
|
+
const supabase = createSupabaseCompatClient(source);
|
|
36
|
+
const { data, error, count } = await supabase.from('approval_steps').select('id', { head: true, count: 'exact' }).eq('id', '1');
|
|
37
|
+
if (error)
|
|
38
|
+
throw error;
|
|
39
|
+
const empty = data;
|
|
40
|
+
const dynamic = await supabase.from('approval_steps').select('id', options);
|
|
41
|
+
if (!dynamic.error) {
|
|
42
|
+
// @ts-expect-error a dynamic head option can produce null on success.
|
|
43
|
+
const rows = dynamic.data;
|
|
44
|
+
}
|
|
45
|
+
const selected = await supabase.from('approval_steps').select('id', { head: true }).select('*');
|
|
46
|
+
if (!selected.error) {
|
|
47
|
+
const rows = selected.data;
|
|
48
|
+
}
|
|
49
|
+
return count;
|
|
50
|
+
}
|
|
51
|
+
async function knownTableUnion(table) {
|
|
52
|
+
const supabase = createSupabaseCompatClient(source);
|
|
53
|
+
const { data, error } = await supabase.from(table).select('*');
|
|
54
|
+
if (error)
|
|
55
|
+
throw error;
|
|
56
|
+
return data[0]?.id;
|
|
57
|
+
}
|
|
58
|
+
async function unknownTableRemainsPermissive(table) {
|
|
59
|
+
const supabase = createSupabaseCompatClient(source);
|
|
60
|
+
const { data } = await supabase.from(table).select('*');
|
|
61
|
+
const value = data?.[0]?.id;
|
|
62
|
+
// @ts-expect-error an unknown table cannot promise a typed id.
|
|
63
|
+
const id = value;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function contactWithProject(row) {
|
|
67
|
+
if (!('projects' in row))
|
|
68
|
+
throw new Error('contacts selection omitted projects');
|
|
69
|
+
const project = row.projects;
|
|
70
|
+
if (project === null)
|
|
71
|
+
return { ...row, projects: null };
|
|
72
|
+
if (typeof project !== 'object' || !('name' in project) || typeof project.name !== 'string') {
|
|
73
|
+
throw new Error('invalid selected project');
|
|
74
|
+
}
|
|
75
|
+
return { ...row, projects: { name: project.name } };
|
|
76
|
+
}
|
|
77
|
+
async function checkedNestedSelection() {
|
|
78
|
+
const supabase = createSupabaseCompatClient(source);
|
|
79
|
+
const response = decodeSupabaseCompatResponse(await supabase.from('contacts').select('*, projects(name)'), rows => rows.map(contactWithProject));
|
|
80
|
+
const { data, error } = response;
|
|
81
|
+
if (error)
|
|
82
|
+
throw error;
|
|
83
|
+
const row = data[0];
|
|
84
|
+
if (!row)
|
|
85
|
+
return null;
|
|
86
|
+
const originalColumn = row.project_id;
|
|
87
|
+
return row.projects?.name ?? originalColumn;
|
|
88
|
+
}
|
|
89
|
+
export const _compatTypingChecks = {
|
|
90
|
+
typedRows, untypedDefault, singleAfterErrorGuard, maybeSingleStillAllowsNoRow,
|
|
91
|
+
headCount, knownTableUnion, unknownTableRemainsPermissive, checkedNestedSelection,
|
|
92
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isomorph.ai/app-sdk",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"test": "npm run build && node --test test/*.test.mjs"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"typescript": "5.9.2"
|
|
14
|
+
}
|
|
15
|
+
}
|