@tiangong-lca/cli 0.0.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/README.md +368 -0
- package/bin/tiangong.d.ts +1 -0
- package/bin/tiangong.js +32 -0
- package/dist/scripts/assert-full-coverage.js +77 -0
- package/dist/scripts/assert-full-coverage.js.map +1 -0
- package/dist/src/cli.js +2740 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/lib/artifacts.js +86 -0
- package/dist/src/lib/artifacts.js.map +1 -0
- package/dist/src/lib/dotenv.js +36 -0
- package/dist/src/lib/dotenv.js.map +1 -0
- package/dist/src/lib/env.js +139 -0
- package/dist/src/lib/env.js.map +1 -0
- package/dist/src/lib/errors.js +38 -0
- package/dist/src/lib/errors.js.map +1 -0
- package/dist/src/lib/flow-build-alias-map.js +299 -0
- package/dist/src/lib/flow-build-alias-map.js.map +1 -0
- package/dist/src/lib/flow-get.js +81 -0
- package/dist/src/lib/flow-get.js.map +1 -0
- package/dist/src/lib/flow-governance.js +164 -0
- package/dist/src/lib/flow-governance.js.map +1 -0
- package/dist/src/lib/flow-list.js +187 -0
- package/dist/src/lib/flow-list.js.map +1 -0
- package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
- package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
- package/dist/src/lib/flow-publish-version.js +541 -0
- package/dist/src/lib/flow-publish-version.js.map +1 -0
- package/dist/src/lib/flow-read.js +310 -0
- package/dist/src/lib/flow-read.js.map +1 -0
- package/dist/src/lib/flow-regen-product.js +1396 -0
- package/dist/src/lib/flow-regen-product.js.map +1 -0
- package/dist/src/lib/flow-remediate.js +821 -0
- package/dist/src/lib/flow-remediate.js.map +1 -0
- package/dist/src/lib/http.js +45 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/io.js +30 -0
- package/dist/src/lib/io.js.map +1 -0
- package/dist/src/lib/kb-search.js +83 -0
- package/dist/src/lib/kb-search.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
- package/dist/src/lib/llm.js +383 -0
- package/dist/src/lib/llm.js.map +1 -0
- package/dist/src/lib/process-auto-build.js +717 -0
- package/dist/src/lib/process-auto-build.js.map +1 -0
- package/dist/src/lib/process-batch-build.js +409 -0
- package/dist/src/lib/process-batch-build.js.map +1 -0
- package/dist/src/lib/process-get.js +66 -0
- package/dist/src/lib/process-get.js.map +1 -0
- package/dist/src/lib/process-publish-build.js +451 -0
- package/dist/src/lib/process-publish-build.js.map +1 -0
- package/dist/src/lib/process-resume-build.js +364 -0
- package/dist/src/lib/process-resume-build.js.map +1 -0
- package/dist/src/lib/publish.js +562 -0
- package/dist/src/lib/publish.js.map +1 -0
- package/dist/src/lib/remote.js +88 -0
- package/dist/src/lib/remote.js.map +1 -0
- package/dist/src/lib/review-flow.js +1110 -0
- package/dist/src/lib/review-flow.js.map +1 -0
- package/dist/src/lib/review-lifecyclemodel.js +727 -0
- package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
- package/dist/src/lib/review-process.js +702 -0
- package/dist/src/lib/review-process.js.map +1 -0
- package/dist/src/lib/run.js +106 -0
- package/dist/src/lib/run.js.map +1 -0
- package/dist/src/lib/state-lock.js +150 -0
- package/dist/src/lib/state-lock.js.map +1 -0
- package/dist/src/lib/supabase-client.js +269 -0
- package/dist/src/lib/supabase-client.js.map +1 -0
- package/dist/src/lib/supabase-json-ordered-write.js +186 -0
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
- package/dist/src/lib/supabase-rest.js +138 -0
- package/dist/src/lib/supabase-rest.js.map +1 -0
- package/dist/src/lib/supabase-session.js +424 -0
- package/dist/src/lib/supabase-session.js.map +1 -0
- package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
- package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
- package/dist/src/lib/unstructured.js +203 -0
- package/dist/src/lib/unstructured.js.map +1 -0
- package/dist/src/lib/user-api-key.js +75 -0
- package/dist/src/lib/user-api-key.js.map +1 -0
- package/dist/src/lib/validation.js +191 -0
- package/dist/src/lib/validation.js.map +1 -0
- package/dist/src/main.js +38 -0
- package/dist/src/main.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
function trimToken(value) {
|
|
4
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
5
|
+
}
|
|
6
|
+
function parseBooleanEnv(value) {
|
|
7
|
+
if (typeof value !== 'string') {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
switch (value.trim().toLowerCase()) {
|
|
11
|
+
case '1':
|
|
12
|
+
case 'true':
|
|
13
|
+
case 'yes':
|
|
14
|
+
case 'on':
|
|
15
|
+
return true;
|
|
16
|
+
default:
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function requireSupabaseRestRuntime(env) {
|
|
21
|
+
const apiBaseUrl = trimToken(env.TIANGONG_LCA_API_BASE_URL);
|
|
22
|
+
const userApiKey = trimToken(env.TIANGONG_LCA_API_KEY);
|
|
23
|
+
const publishableKey = trimToken(env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY);
|
|
24
|
+
const sessionFile = trimToken(env.TIANGONG_LCA_SESSION_FILE) || null;
|
|
25
|
+
const disableSessionCache = parseBooleanEnv(env.TIANGONG_LCA_DISABLE_SESSION_CACHE);
|
|
26
|
+
const forceReauth = parseBooleanEnv(env.TIANGONG_LCA_FORCE_REAUTH);
|
|
27
|
+
const missing = [];
|
|
28
|
+
if (!apiBaseUrl) {
|
|
29
|
+
missing.push('TIANGONG_LCA_API_BASE_URL');
|
|
30
|
+
}
|
|
31
|
+
if (!userApiKey) {
|
|
32
|
+
missing.push('TIANGONG_LCA_API_KEY');
|
|
33
|
+
}
|
|
34
|
+
if (!publishableKey) {
|
|
35
|
+
missing.push('TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY');
|
|
36
|
+
}
|
|
37
|
+
if (missing.length > 0) {
|
|
38
|
+
throw new CliError(`Missing Supabase REST runtime env: ${missing.join(', ')}`, {
|
|
39
|
+
code: 'SUPABASE_REST_ENV_REQUIRED',
|
|
40
|
+
exitCode: 2,
|
|
41
|
+
details: { missing },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
apiBaseUrl,
|
|
46
|
+
userApiKey,
|
|
47
|
+
publishableKey,
|
|
48
|
+
sessionFile,
|
|
49
|
+
disableSessionCache,
|
|
50
|
+
forceReauth,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function normalizeBaseUrl(apiBaseUrl) {
|
|
54
|
+
const normalized = apiBaseUrl.trim().replace(/\/+$/u, '');
|
|
55
|
+
if (!normalized) {
|
|
56
|
+
throw new CliError('Cannot derive Supabase URLs from an empty API base URL.', {
|
|
57
|
+
code: 'SUPABASE_REST_BASE_URL_INVALID',
|
|
58
|
+
exitCode: 2,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
export function deriveSupabaseProjectBaseUrl(apiBaseUrl) {
|
|
64
|
+
const normalized = normalizeBaseUrl(apiBaseUrl);
|
|
65
|
+
if (normalized.endsWith('/functions/v1')) {
|
|
66
|
+
return normalized.replace(/\/functions\/v1$/u, '');
|
|
67
|
+
}
|
|
68
|
+
if (normalized.endsWith('/rest/v1')) {
|
|
69
|
+
return normalized.replace(/\/rest\/v1$/u, '');
|
|
70
|
+
}
|
|
71
|
+
if (/^https?:\/\/[^/]+$/u.test(normalized)) {
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
throw new CliError('Cannot derive a Supabase project base URL from TIANGONG_LCA_API_BASE_URL. Use a Supabase project base URL, a /functions/v1 base URL, or a /rest/v1 base URL.', {
|
|
75
|
+
code: 'SUPABASE_REST_BASE_URL_INVALID',
|
|
76
|
+
exitCode: 2,
|
|
77
|
+
details: normalized,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function deriveSupabaseRestBaseUrl(apiBaseUrl) {
|
|
81
|
+
return `${deriveSupabaseProjectBaseUrl(apiBaseUrl)}/rest/v1`;
|
|
82
|
+
}
|
|
83
|
+
function mergeSignals(signal, timeoutMs) {
|
|
84
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
85
|
+
return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
86
|
+
}
|
|
87
|
+
export function buildSupabaseAuthHeaders(publishableKey, accessToken) {
|
|
88
|
+
return {
|
|
89
|
+
Accept: 'application/json',
|
|
90
|
+
Authorization: `Bearer ${accessToken}`,
|
|
91
|
+
apikey: publishableKey,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function toResponse(responseLike, method) {
|
|
95
|
+
if (responseLike instanceof Response) {
|
|
96
|
+
return responseLike;
|
|
97
|
+
}
|
|
98
|
+
const body = await responseLike.text();
|
|
99
|
+
const contentType = responseLike.headers.get('content-type') ?? '';
|
|
100
|
+
const normalizedBody = responseLike.ok &&
|
|
101
|
+
method !== undefined &&
|
|
102
|
+
!['GET', 'HEAD'].includes(method.toUpperCase()) &&
|
|
103
|
+
body.length > 0 &&
|
|
104
|
+
!contentType.includes('application/json')
|
|
105
|
+
? ''
|
|
106
|
+
: body;
|
|
107
|
+
const headers = new Headers();
|
|
108
|
+
for (const headerName of ['content-type', 'content-range', 'location']) {
|
|
109
|
+
const value = responseLike.headers.get(headerName);
|
|
110
|
+
if (value) {
|
|
111
|
+
headers.set(headerName, value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return new Response(normalizedBody, {
|
|
115
|
+
status: responseLike.status,
|
|
116
|
+
headers,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function buildResolvedHeaders(initHeaders, publishableKey, accessToken) {
|
|
120
|
+
const headers = new Headers(initHeaders);
|
|
121
|
+
const authHeaders = buildSupabaseAuthHeaders(publishableKey, accessToken);
|
|
122
|
+
Object.entries(authHeaders).forEach(([headerName, value]) => {
|
|
123
|
+
headers.set(headerName, value);
|
|
124
|
+
});
|
|
125
|
+
return headers;
|
|
126
|
+
}
|
|
127
|
+
async function performFetch(input, init, fetchImpl, timeoutMs) {
|
|
128
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
129
|
+
try {
|
|
130
|
+
return await fetchImpl(url, {
|
|
131
|
+
...init,
|
|
132
|
+
signal: mergeSignals(init?.signal, timeoutMs),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function createSupabaseFetch(fetchImpl, timeoutMs, runtime) {
|
|
140
|
+
return async (input, init) => {
|
|
141
|
+
if (!runtime) {
|
|
142
|
+
const responseLike = await performFetch(input, init, fetchImpl, timeoutMs);
|
|
143
|
+
return toResponse(responseLike, init?.method);
|
|
144
|
+
}
|
|
145
|
+
const accessToken = await runtime.getAccessToken();
|
|
146
|
+
let responseLike = await performFetch(input, {
|
|
147
|
+
...init,
|
|
148
|
+
headers: buildResolvedHeaders(init?.headers, runtime.publishableKey, accessToken),
|
|
149
|
+
}, fetchImpl, timeoutMs);
|
|
150
|
+
if ([401, 403].includes(responseLike.status) &&
|
|
151
|
+
typeof runtime.refreshAccessToken === 'function') {
|
|
152
|
+
const refreshedAccessToken = await runtime.refreshAccessToken();
|
|
153
|
+
responseLike = await performFetch(input, {
|
|
154
|
+
...init,
|
|
155
|
+
headers: buildResolvedHeaders(init?.headers, runtime.publishableKey, refreshedAccessToken),
|
|
156
|
+
}, fetchImpl, timeoutMs);
|
|
157
|
+
}
|
|
158
|
+
return toResponse(responseLike, init?.method);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function formatPostgrestError(error) {
|
|
162
|
+
return {
|
|
163
|
+
code: typeof error.code === 'string' ? error.code : null,
|
|
164
|
+
message: error.message,
|
|
165
|
+
details: typeof error.details === 'string' ? error.details : null,
|
|
166
|
+
hint: typeof error.hint === 'string' ? error.hint : null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export async function runSupabaseArrayQuery(queryPromise, sourceUrl) {
|
|
170
|
+
const data = await runSupabaseQuery(queryPromise, sourceUrl);
|
|
171
|
+
return (data ?? []);
|
|
172
|
+
}
|
|
173
|
+
export async function runSupabaseQuery(queryPromise, sourceUrl) {
|
|
174
|
+
try {
|
|
175
|
+
const result = await queryPromise;
|
|
176
|
+
if (result.error) {
|
|
177
|
+
if (result.status === 0 &&
|
|
178
|
+
result.error.code === '' &&
|
|
179
|
+
/^SyntaxError:/u.test(result.error.message)) {
|
|
180
|
+
throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {
|
|
181
|
+
code: 'REMOTE_INVALID_JSON',
|
|
182
|
+
exitCode: 1,
|
|
183
|
+
details: result.error.details || result.error.message,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
throw new CliError(`HTTP ${result.status} returned from ${sourceUrl}`, {
|
|
187
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
188
|
+
exitCode: 1,
|
|
189
|
+
details: formatPostgrestError(result.error),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return result.data;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof CliError) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
if (error instanceof SyntaxError) {
|
|
199
|
+
throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {
|
|
200
|
+
code: 'REMOTE_INVALID_JSON',
|
|
201
|
+
exitCode: 1,
|
|
202
|
+
details: error.message,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
throw new CliError(`Supabase request failed for ${sourceUrl}`, {
|
|
206
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
207
|
+
exitCode: 1,
|
|
208
|
+
details: error instanceof Error ? error.message : String(error),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function runSupabaseMutation(queryPromise, sourceUrl) {
|
|
213
|
+
try {
|
|
214
|
+
const result = await queryPromise;
|
|
215
|
+
if (result.error) {
|
|
216
|
+
if (result.status === 0 &&
|
|
217
|
+
result.error.code === '' &&
|
|
218
|
+
/^SyntaxError:/u.test(result.error.message)) {
|
|
219
|
+
throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {
|
|
220
|
+
code: 'REMOTE_INVALID_JSON',
|
|
221
|
+
exitCode: 1,
|
|
222
|
+
details: result.error.details || result.error.message,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
throw new CliError(`HTTP ${result.status} returned from ${sourceUrl}`, {
|
|
226
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
227
|
+
exitCode: 1,
|
|
228
|
+
details: formatPostgrestError(result.error),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (error instanceof CliError) {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
if (error instanceof SyntaxError) {
|
|
237
|
+
throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {
|
|
238
|
+
code: 'REMOTE_INVALID_JSON',
|
|
239
|
+
exitCode: 1,
|
|
240
|
+
details: error.message,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
throw new CliError(`Supabase request failed for ${sourceUrl}`, {
|
|
244
|
+
code: 'REMOTE_REQUEST_FAILED',
|
|
245
|
+
exitCode: 1,
|
|
246
|
+
details: error instanceof Error ? error.message : String(error),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export function createSupabaseDataClient(runtime, fetchImpl, timeoutMs) {
|
|
251
|
+
return {
|
|
252
|
+
client: createClient(deriveSupabaseProjectBaseUrl(runtime.apiBaseUrl), runtime.publishableKey, {
|
|
253
|
+
auth: {
|
|
254
|
+
autoRefreshToken: false,
|
|
255
|
+
persistSession: false,
|
|
256
|
+
detectSessionInUrl: false,
|
|
257
|
+
},
|
|
258
|
+
global: {
|
|
259
|
+
fetch: createSupabaseFetch(fetchImpl, timeoutMs, runtime),
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
restBaseUrl: deriveSupabaseRestBaseUrl(runtime.apiBaseUrl),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
export const __testInternals = {
|
|
266
|
+
parseBooleanEnv,
|
|
267
|
+
trimToken,
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=supabase-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase-client.js","sourceRoot":"","sources":["../../../src/lib/supabase-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { createClient, type PostgrestError } from '@supabase/supabase-js';\nimport { CliError } from './errors.js';\nimport type { FetchLike, ResponseLike } from './http.js';\n\nexport type SupabaseRestRuntime = {\n apiBaseUrl: string;\n userApiKey: string;\n publishableKey: string;\n sessionFile: string | null;\n disableSessionCache: boolean;\n forceReauth: boolean;\n};\n\nexport type SupabaseDataRuntime = {\n apiBaseUrl: string;\n publishableKey: string;\n getAccessToken: () => Promise<string>;\n refreshAccessToken?: () => Promise<string>;\n};\n\nfunction trimToken(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction parseBooleanEnv(value: unknown): boolean {\n if (typeof value !== 'string') {\n return false;\n }\n\n switch (value.trim().toLowerCase()) {\n case '1':\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n}\n\nexport function requireSupabaseRestRuntime(env: NodeJS.ProcessEnv): SupabaseRestRuntime {\n const apiBaseUrl = trimToken(env.TIANGONG_LCA_API_BASE_URL);\n const userApiKey = trimToken(env.TIANGONG_LCA_API_KEY);\n const publishableKey = trimToken(env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY);\n const sessionFile = trimToken(env.TIANGONG_LCA_SESSION_FILE) || null;\n const disableSessionCache = parseBooleanEnv(env.TIANGONG_LCA_DISABLE_SESSION_CACHE);\n const forceReauth = parseBooleanEnv(env.TIANGONG_LCA_FORCE_REAUTH);\n const missing: string[] = [];\n\n if (!apiBaseUrl) {\n missing.push('TIANGONG_LCA_API_BASE_URL');\n }\n\n if (!userApiKey) {\n missing.push('TIANGONG_LCA_API_KEY');\n }\n\n if (!publishableKey) {\n missing.push('TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY');\n }\n\n if (missing.length > 0) {\n throw new CliError(`Missing Supabase REST runtime env: ${missing.join(', ')}`, {\n code: 'SUPABASE_REST_ENV_REQUIRED',\n exitCode: 2,\n details: { missing },\n });\n }\n\n return {\n apiBaseUrl,\n userApiKey,\n publishableKey,\n sessionFile,\n disableSessionCache,\n forceReauth,\n };\n}\n\nfunction normalizeBaseUrl(apiBaseUrl: string): string {\n const normalized = apiBaseUrl.trim().replace(/\\/+$/u, '');\n if (!normalized) {\n throw new CliError('Cannot derive Supabase URLs from an empty API base URL.', {\n code: 'SUPABASE_REST_BASE_URL_INVALID',\n exitCode: 2,\n });\n }\n return normalized;\n}\n\nexport function deriveSupabaseProjectBaseUrl(apiBaseUrl: string): string {\n const normalized = normalizeBaseUrl(apiBaseUrl);\n\n if (normalized.endsWith('/functions/v1')) {\n return normalized.replace(/\\/functions\\/v1$/u, '');\n }\n\n if (normalized.endsWith('/rest/v1')) {\n return normalized.replace(/\\/rest\\/v1$/u, '');\n }\n\n if (/^https?:\\/\\/[^/]+$/u.test(normalized)) {\n return normalized;\n }\n\n throw new CliError(\n 'Cannot derive a Supabase project base URL from TIANGONG_LCA_API_BASE_URL. Use a Supabase project base URL, a /functions/v1 base URL, or a /rest/v1 base URL.',\n {\n code: 'SUPABASE_REST_BASE_URL_INVALID',\n exitCode: 2,\n details: normalized,\n },\n );\n}\n\nexport function deriveSupabaseRestBaseUrl(apiBaseUrl: string): string {\n return `${deriveSupabaseProjectBaseUrl(apiBaseUrl)}/rest/v1`;\n}\n\nfunction mergeSignals(signal: AbortSignal | null | undefined, timeoutMs: number): AbortSignal {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;\n}\n\nexport function buildSupabaseAuthHeaders(\n publishableKey: string,\n accessToken: string,\n): Record<string, string> {\n return {\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n apikey: publishableKey,\n };\n}\n\nasync function toResponse(\n responseLike: ResponseLike,\n method: string | undefined,\n): Promise<Response> {\n if (responseLike instanceof Response) {\n return responseLike;\n }\n\n const body = await responseLike.text();\n const contentType = responseLike.headers.get('content-type') ?? '';\n const normalizedBody =\n responseLike.ok &&\n method !== undefined &&\n !['GET', 'HEAD'].includes(method.toUpperCase()) &&\n body.length > 0 &&\n !contentType.includes('application/json')\n ? ''\n : body;\n const headers = new Headers();\n for (const headerName of ['content-type', 'content-range', 'location']) {\n const value = responseLike.headers.get(headerName);\n if (value) {\n headers.set(headerName, value);\n }\n }\n\n return new Response(normalizedBody, {\n status: responseLike.status,\n headers,\n });\n}\n\nfunction buildResolvedHeaders(\n initHeaders: HeadersInit | undefined,\n publishableKey: string,\n accessToken: string,\n): Headers {\n const headers = new Headers(initHeaders);\n const authHeaders = buildSupabaseAuthHeaders(publishableKey, accessToken);\n\n Object.entries(authHeaders).forEach(([headerName, value]) => {\n headers.set(headerName, value);\n });\n\n return headers;\n}\n\nasync function performFetch(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n fetchImpl: FetchLike,\n timeoutMs: number,\n): Promise<ResponseLike> {\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n\n try {\n return await fetchImpl(url, {\n ...init,\n signal: mergeSignals(init?.signal, timeoutMs),\n });\n } catch (error) {\n throw error instanceof Error ? error : new Error(String(error));\n }\n}\n\nexport function createSupabaseFetch(\n fetchImpl: FetchLike,\n timeoutMs: number,\n runtime?: SupabaseDataRuntime,\n): typeof fetch {\n return async (input, init) => {\n if (!runtime) {\n const responseLike = await performFetch(input, init, fetchImpl, timeoutMs);\n return toResponse(responseLike, init?.method);\n }\n\n const accessToken = await runtime.getAccessToken();\n let responseLike = await performFetch(\n input,\n {\n ...init,\n headers: buildResolvedHeaders(init?.headers, runtime.publishableKey, accessToken),\n },\n fetchImpl,\n timeoutMs,\n );\n\n if (\n [401, 403].includes(responseLike.status) &&\n typeof runtime.refreshAccessToken === 'function'\n ) {\n const refreshedAccessToken = await runtime.refreshAccessToken();\n responseLike = await performFetch(\n input,\n {\n ...init,\n headers: buildResolvedHeaders(\n init?.headers,\n runtime.publishableKey,\n refreshedAccessToken,\n ),\n },\n fetchImpl,\n timeoutMs,\n );\n }\n\n return toResponse(responseLike, init?.method);\n };\n}\n\ntype SupabaseQueryResult<T> = {\n data: T | null;\n error: PostgrestError | null;\n status: number;\n};\n\nfunction formatPostgrestError(error: PostgrestError): {\n code: string | null;\n message: string;\n details: string | null;\n hint: string | null;\n} {\n return {\n code: typeof error.code === 'string' ? error.code : null,\n message: error.message,\n details: typeof error.details === 'string' ? error.details : null,\n hint: typeof error.hint === 'string' ? error.hint : null,\n };\n}\n\nexport async function runSupabaseArrayQuery<T>(\n queryPromise: PromiseLike<SupabaseQueryResult<T[]>>,\n sourceUrl: string,\n): Promise<T[]> {\n const data = await runSupabaseQuery(queryPromise, sourceUrl);\n return (data ?? []) as T[];\n}\n\nexport async function runSupabaseQuery<T>(\n queryPromise: PromiseLike<SupabaseQueryResult<T>>,\n sourceUrl: string,\n): Promise<T | null> {\n try {\n const result = await queryPromise;\n if (result.error) {\n if (\n result.status === 0 &&\n result.error.code === '' &&\n /^SyntaxError:/u.test(result.error.message)\n ) {\n throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {\n code: 'REMOTE_INVALID_JSON',\n exitCode: 1,\n details: result.error.details || result.error.message,\n });\n }\n\n throw new CliError(`HTTP ${result.status} returned from ${sourceUrl}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: formatPostgrestError(result.error),\n });\n }\n return result.data;\n } catch (error) {\n if (error instanceof CliError) {\n throw error;\n }\n\n if (error instanceof SyntaxError) {\n throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {\n code: 'REMOTE_INVALID_JSON',\n exitCode: 1,\n details: error.message,\n });\n }\n\n throw new CliError(`Supabase request failed for ${sourceUrl}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nexport async function runSupabaseMutation(\n queryPromise: PromiseLike<{\n error: PostgrestError | null;\n status: number;\n }>,\n sourceUrl: string,\n): Promise<void> {\n try {\n const result = await queryPromise;\n if (result.error) {\n if (\n result.status === 0 &&\n result.error.code === '' &&\n /^SyntaxError:/u.test(result.error.message)\n ) {\n throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {\n code: 'REMOTE_INVALID_JSON',\n exitCode: 1,\n details: result.error.details || result.error.message,\n });\n }\n\n throw new CliError(`HTTP ${result.status} returned from ${sourceUrl}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: formatPostgrestError(result.error),\n });\n }\n } catch (error) {\n if (error instanceof CliError) {\n throw error;\n }\n\n if (error instanceof SyntaxError) {\n throw new CliError(`Remote response was not valid JSON for ${sourceUrl}`, {\n code: 'REMOTE_INVALID_JSON',\n exitCode: 1,\n details: error.message,\n });\n }\n\n throw new CliError(`Supabase request failed for ${sourceUrl}`, {\n code: 'REMOTE_REQUEST_FAILED',\n exitCode: 1,\n details: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nexport function createSupabaseDataClient(\n runtime: SupabaseDataRuntime,\n fetchImpl: FetchLike,\n timeoutMs: number,\n) {\n return {\n client: createClient(deriveSupabaseProjectBaseUrl(runtime.apiBaseUrl), runtime.publishableKey, {\n auth: {\n autoRefreshToken: false,\n persistSession: false,\n detectSessionInUrl: false,\n },\n global: {\n fetch: createSupabaseFetch(fetchImpl, timeoutMs, runtime),\n },\n }),\n restBaseUrl: deriveSupabaseRestBaseUrl(runtime.apiBaseUrl),\n };\n}\n\nexport const __testInternals = {\n parseBooleanEnv,\n trimToken,\n};\n"]}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { CliError } from './errors.js';
|
|
2
|
+
import { createSupabaseDataClient, requireSupabaseRestRuntime, runSupabaseArrayQuery, runSupabaseMutation, } from './supabase-client.js';
|
|
3
|
+
import { createSupabaseDataRuntime } from './supabase-session.js';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function trimToken(value) {
|
|
9
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
10
|
+
}
|
|
11
|
+
function buildSelectUrl(restBaseUrl, table, id, version) {
|
|
12
|
+
const url = new URL(`${restBaseUrl.replace(/\/+$/u, '')}/${table}`);
|
|
13
|
+
url.searchParams.set('select', 'id,version,state_code');
|
|
14
|
+
url.searchParams.set('id', `eq.${id}`);
|
|
15
|
+
url.searchParams.set('version', `eq.${version}`);
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
function buildUpdateUrl(restBaseUrl, table, id, version) {
|
|
19
|
+
const url = new URL(`${restBaseUrl.replace(/\/+$/u, '')}/${table}`);
|
|
20
|
+
url.searchParams.set('id', `eq.${id}`);
|
|
21
|
+
url.searchParams.set('version', `eq.${version}`);
|
|
22
|
+
return url.toString();
|
|
23
|
+
}
|
|
24
|
+
function parseVisibleRows(payload, url) {
|
|
25
|
+
if (!Array.isArray(payload)) {
|
|
26
|
+
throw new CliError(`Supabase REST response was not a JSON array for ${url}`, {
|
|
27
|
+
code: 'SUPABASE_REST_RESPONSE_INVALID',
|
|
28
|
+
exitCode: 1,
|
|
29
|
+
details: payload,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return payload.map((item, index) => {
|
|
33
|
+
if (!isRecord(item)) {
|
|
34
|
+
throw new CliError(`Supabase REST row ${index} was not a JSON object for ${url}`, {
|
|
35
|
+
code: 'SUPABASE_REST_RESPONSE_INVALID',
|
|
36
|
+
exitCode: 1,
|
|
37
|
+
details: item,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
id: trimToken(item.id),
|
|
42
|
+
version: trimToken(item.version),
|
|
43
|
+
state_code: typeof item.state_code === 'number' ? item.state_code : null,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function exactVisibleRows(options) {
|
|
48
|
+
const url = buildSelectUrl(options.restBaseUrl, options.table, options.id, options.version);
|
|
49
|
+
const payload = await runSupabaseArrayQuery(options.client
|
|
50
|
+
.from(options.table)
|
|
51
|
+
.select('id,version,state_code')
|
|
52
|
+
.eq('id', options.id)
|
|
53
|
+
.eq('version', options.version), url);
|
|
54
|
+
return parseVisibleRows(payload, url);
|
|
55
|
+
}
|
|
56
|
+
async function insertJsonOrderedRow(options) {
|
|
57
|
+
const url = `${options.restBaseUrl.replace(/\/+$/u, '')}/${options.table}`;
|
|
58
|
+
await runSupabaseMutation(options.client.from(options.table).insert({
|
|
59
|
+
id: options.id,
|
|
60
|
+
json_ordered: options.payload,
|
|
61
|
+
...(options.extraData ?? {}),
|
|
62
|
+
}), url);
|
|
63
|
+
}
|
|
64
|
+
async function updateJsonOrderedRow(options) {
|
|
65
|
+
const url = buildUpdateUrl(options.restBaseUrl, options.table, options.id, options.version);
|
|
66
|
+
await runSupabaseMutation(options.client
|
|
67
|
+
.from(options.table)
|
|
68
|
+
.update({
|
|
69
|
+
json_ordered: options.payload,
|
|
70
|
+
...(options.extraData ?? {}),
|
|
71
|
+
})
|
|
72
|
+
.eq('id', options.id)
|
|
73
|
+
.eq('version', options.version), url);
|
|
74
|
+
}
|
|
75
|
+
function requireNonEmptyToken(value, label, code) {
|
|
76
|
+
const normalized = value.trim();
|
|
77
|
+
if (!normalized) {
|
|
78
|
+
throw new CliError(`Missing required ${label}.`, {
|
|
79
|
+
code,
|
|
80
|
+
exitCode: 2,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
export function hasSupabaseRestRuntime(env) {
|
|
86
|
+
if (!env) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return Boolean(trimToken(env.TIANGONG_LCA_API_BASE_URL) &&
|
|
90
|
+
trimToken(env.TIANGONG_LCA_API_KEY) &&
|
|
91
|
+
trimToken(env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY));
|
|
92
|
+
}
|
|
93
|
+
export async function syncSupabaseJsonOrderedRecord(options) {
|
|
94
|
+
const id = requireNonEmptyToken(options.id, 'dataset id', 'SUPABASE_JSON_ORDERED_ID_REQUIRED');
|
|
95
|
+
const version = requireNonEmptyToken(options.version, 'dataset version', 'SUPABASE_JSON_ORDERED_VERSION_REQUIRED');
|
|
96
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
97
|
+
const runtime = createSupabaseDataRuntime({
|
|
98
|
+
runtime: requireSupabaseRestRuntime(options.env),
|
|
99
|
+
fetchImpl: options.fetchImpl,
|
|
100
|
+
timeoutMs,
|
|
101
|
+
});
|
|
102
|
+
const { client, restBaseUrl } = createSupabaseDataClient(runtime, options.fetchImpl, timeoutMs);
|
|
103
|
+
const visibleBefore = await exactVisibleRows({
|
|
104
|
+
client,
|
|
105
|
+
restBaseUrl,
|
|
106
|
+
table: options.table,
|
|
107
|
+
id,
|
|
108
|
+
version,
|
|
109
|
+
});
|
|
110
|
+
if (options.writeMode === 'append_only_insert' && visibleBefore.length > 0) {
|
|
111
|
+
return {
|
|
112
|
+
status: 'success',
|
|
113
|
+
operation: 'skipped_existing',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (visibleBefore.length > 0) {
|
|
117
|
+
await updateJsonOrderedRow({
|
|
118
|
+
client,
|
|
119
|
+
restBaseUrl,
|
|
120
|
+
table: options.table,
|
|
121
|
+
id,
|
|
122
|
+
version,
|
|
123
|
+
payload: options.payload,
|
|
124
|
+
extraData: options.extraData,
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
status: 'success',
|
|
128
|
+
operation: 'update_existing',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await insertJsonOrderedRow({
|
|
133
|
+
client,
|
|
134
|
+
restBaseUrl,
|
|
135
|
+
table: options.table,
|
|
136
|
+
id,
|
|
137
|
+
payload: options.payload,
|
|
138
|
+
extraData: options.extraData,
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
status: 'success',
|
|
142
|
+
operation: 'insert',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const visibleAfter = await exactVisibleRows({
|
|
147
|
+
client,
|
|
148
|
+
restBaseUrl,
|
|
149
|
+
table: options.table,
|
|
150
|
+
id,
|
|
151
|
+
version,
|
|
152
|
+
});
|
|
153
|
+
if (visibleAfter.length === 0) {
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
if (options.writeMode === 'append_only_insert') {
|
|
157
|
+
return {
|
|
158
|
+
status: 'success',
|
|
159
|
+
operation: 'skipped_existing',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
await updateJsonOrderedRow({
|
|
163
|
+
client,
|
|
164
|
+
restBaseUrl,
|
|
165
|
+
table: options.table,
|
|
166
|
+
id,
|
|
167
|
+
version,
|
|
168
|
+
payload: options.payload,
|
|
169
|
+
extraData: options.extraData,
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
status: 'success',
|
|
173
|
+
operation: 'update_after_insert_error',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export const __testInternals = {
|
|
178
|
+
buildSelectUrl,
|
|
179
|
+
buildUpdateUrl,
|
|
180
|
+
parseVisibleRows,
|
|
181
|
+
exactVisibleRows,
|
|
182
|
+
insertJsonOrderedRow,
|
|
183
|
+
updateJsonOrderedRow,
|
|
184
|
+
requireNonEmptyToken,
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=supabase-json-ordered-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase-json-ordered-write.js","sourceRoot":"","sources":["../../../src/lib/supabase-json-ordered-write.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors.js';\nimport type { FetchLike } from './http.js';\nimport {\n createSupabaseDataClient,\n requireSupabaseRestRuntime,\n runSupabaseArrayQuery,\n runSupabaseMutation,\n} from './supabase-client.js';\nimport { createSupabaseDataRuntime } from './supabase-session.js';\n\ntype JsonObject = Record<string, unknown>;\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport type SupabaseJsonOrderedTable = 'lifecyclemodels' | 'processes' | 'sources';\nexport type SupabaseJsonOrderedWriteMode = 'upsert_current_version' | 'append_only_insert';\nexport type SupabaseJsonOrderedWriteOperation =\n | 'insert'\n | 'update_existing'\n | 'update_after_insert_error'\n | 'skipped_existing';\n\nexport type SupabaseJsonOrderedWriteResult = {\n status: 'success';\n operation: SupabaseJsonOrderedWriteOperation;\n};\n\ntype VisibleRow = {\n id: string;\n version: string;\n state_code: number | null;\n};\n\ntype SupabaseDataClient = ReturnType<typeof createSupabaseDataClient>['client'];\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction trimToken(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction buildSelectUrl(\n restBaseUrl: string,\n table: SupabaseJsonOrderedTable,\n id: string,\n version: string,\n): string {\n const url = new URL(`${restBaseUrl.replace(/\\/+$/u, '')}/${table}`);\n url.searchParams.set('select', 'id,version,state_code');\n url.searchParams.set('id', `eq.${id}`);\n url.searchParams.set('version', `eq.${version}`);\n return url.toString();\n}\n\nfunction buildUpdateUrl(\n restBaseUrl: string,\n table: SupabaseJsonOrderedTable,\n id: string,\n version: string,\n): string {\n const url = new URL(`${restBaseUrl.replace(/\\/+$/u, '')}/${table}`);\n url.searchParams.set('id', `eq.${id}`);\n url.searchParams.set('version', `eq.${version}`);\n return url.toString();\n}\n\nfunction parseVisibleRows(payload: unknown, url: string): VisibleRow[] {\n if (!Array.isArray(payload)) {\n throw new CliError(`Supabase REST response was not a JSON array for ${url}`, {\n code: 'SUPABASE_REST_RESPONSE_INVALID',\n exitCode: 1,\n details: payload,\n });\n }\n\n return payload.map((item, index) => {\n if (!isRecord(item)) {\n throw new CliError(`Supabase REST row ${index} was not a JSON object for ${url}`, {\n code: 'SUPABASE_REST_RESPONSE_INVALID',\n exitCode: 1,\n details: item,\n });\n }\n\n return {\n id: trimToken(item.id),\n version: trimToken(item.version),\n state_code: typeof item.state_code === 'number' ? item.state_code : null,\n };\n });\n}\n\nasync function exactVisibleRows(options: {\n client: SupabaseDataClient;\n restBaseUrl: string;\n table: SupabaseJsonOrderedTable;\n id: string;\n version: string;\n}): Promise<VisibleRow[]> {\n const url = buildSelectUrl(options.restBaseUrl, options.table, options.id, options.version);\n const payload = await runSupabaseArrayQuery(\n options.client\n .from(options.table)\n .select('id,version,state_code')\n .eq('id', options.id)\n .eq('version', options.version),\n url,\n );\n return parseVisibleRows(payload, url);\n}\n\nasync function insertJsonOrderedRow(options: {\n client: SupabaseDataClient;\n restBaseUrl: string;\n table: SupabaseJsonOrderedTable;\n id: string;\n payload: JsonObject;\n extraData?: JsonObject;\n}): Promise<void> {\n const url = `${options.restBaseUrl.replace(/\\/+$/u, '')}/${options.table}`;\n await runSupabaseMutation(\n options.client.from(options.table).insert({\n id: options.id,\n json_ordered: options.payload,\n ...(options.extraData ?? {}),\n }),\n url,\n );\n}\n\nasync function updateJsonOrderedRow(options: {\n client: SupabaseDataClient;\n restBaseUrl: string;\n table: SupabaseJsonOrderedTable;\n id: string;\n version: string;\n payload: JsonObject;\n extraData?: JsonObject;\n}): Promise<void> {\n const url = buildUpdateUrl(options.restBaseUrl, options.table, options.id, options.version);\n await runSupabaseMutation(\n options.client\n .from(options.table)\n .update({\n json_ordered: options.payload,\n ...(options.extraData ?? {}),\n })\n .eq('id', options.id)\n .eq('version', options.version),\n url,\n );\n}\n\nfunction requireNonEmptyToken(value: string, label: string, code: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new CliError(`Missing required ${label}.`, {\n code,\n exitCode: 2,\n });\n }\n return normalized;\n}\n\nexport function hasSupabaseRestRuntime(env: NodeJS.ProcessEnv | undefined): boolean {\n if (!env) {\n return false;\n }\n\n return Boolean(\n trimToken(env.TIANGONG_LCA_API_BASE_URL) &&\n trimToken(env.TIANGONG_LCA_API_KEY) &&\n trimToken(env.TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY),\n );\n}\n\nexport async function syncSupabaseJsonOrderedRecord(options: {\n table: SupabaseJsonOrderedTable;\n id: string;\n version: string;\n payload: JsonObject;\n writeMode: SupabaseJsonOrderedWriteMode;\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n timeoutMs?: number;\n extraData?: JsonObject;\n}): Promise<SupabaseJsonOrderedWriteResult> {\n const id = requireNonEmptyToken(options.id, 'dataset id', 'SUPABASE_JSON_ORDERED_ID_REQUIRED');\n const version = requireNonEmptyToken(\n options.version,\n 'dataset version',\n 'SUPABASE_JSON_ORDERED_VERSION_REQUIRED',\n );\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const runtime = createSupabaseDataRuntime({\n runtime: requireSupabaseRestRuntime(options.env),\n fetchImpl: options.fetchImpl,\n timeoutMs,\n });\n const { client, restBaseUrl } = createSupabaseDataClient(runtime, options.fetchImpl, timeoutMs);\n\n const visibleBefore = await exactVisibleRows({\n client,\n restBaseUrl,\n table: options.table,\n id,\n version,\n });\n\n if (options.writeMode === 'append_only_insert' && visibleBefore.length > 0) {\n return {\n status: 'success',\n operation: 'skipped_existing',\n };\n }\n\n if (visibleBefore.length > 0) {\n await updateJsonOrderedRow({\n client,\n restBaseUrl,\n table: options.table,\n id,\n version,\n payload: options.payload,\n extraData: options.extraData,\n });\n return {\n status: 'success',\n operation: 'update_existing',\n };\n }\n\n try {\n await insertJsonOrderedRow({\n client,\n restBaseUrl,\n table: options.table,\n id,\n payload: options.payload,\n extraData: options.extraData,\n });\n return {\n status: 'success',\n operation: 'insert',\n };\n } catch (error) {\n const visibleAfter = await exactVisibleRows({\n client,\n restBaseUrl,\n table: options.table,\n id,\n version,\n });\n\n if (visibleAfter.length === 0) {\n throw error;\n }\n\n if (options.writeMode === 'append_only_insert') {\n return {\n status: 'success',\n operation: 'skipped_existing',\n };\n }\n\n await updateJsonOrderedRow({\n client,\n restBaseUrl,\n table: options.table,\n id,\n version,\n payload: options.payload,\n extraData: options.extraData,\n });\n return {\n status: 'success',\n operation: 'update_after_insert_error',\n };\n }\n}\n\nexport const __testInternals = {\n buildSelectUrl,\n buildUpdateUrl,\n parseVisibleRows,\n exactVisibleRows,\n insertJsonOrderedRow,\n updateJsonOrderedRow,\n requireNonEmptyToken,\n};\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { CliError } from './errors.js';
|
|
2
|
+
import { createSupabaseDataClient, deriveSupabaseRestBaseUrl, requireSupabaseRestRuntime, runSupabaseArrayQuery, } from './supabase-client.js';
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function buildProcessUrl(restBaseUrl, options) {
|
|
7
|
+
const url = new URL(`${restBaseUrl.replace(/\/+$/u, '')}/processes`);
|
|
8
|
+
url.searchParams.set('select', 'id,version,json,modified_at,state_code');
|
|
9
|
+
url.searchParams.set('id', `eq.${options.id}`);
|
|
10
|
+
if (options.version) {
|
|
11
|
+
url.searchParams.set('version', `eq.${options.version}`);
|
|
12
|
+
}
|
|
13
|
+
else if (options.latestOnly) {
|
|
14
|
+
url.searchParams.set('order', 'version.desc');
|
|
15
|
+
url.searchParams.set('limit', '1');
|
|
16
|
+
}
|
|
17
|
+
return url.toString();
|
|
18
|
+
}
|
|
19
|
+
function parseRows(payload, url) {
|
|
20
|
+
if (!Array.isArray(payload)) {
|
|
21
|
+
throw new CliError(`Supabase REST response was not a JSON array for ${url}`, {
|
|
22
|
+
code: 'SUPABASE_REST_RESPONSE_INVALID',
|
|
23
|
+
exitCode: 1,
|
|
24
|
+
details: payload,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return payload.map((item, index) => {
|
|
28
|
+
if (!isRecord(item)) {
|
|
29
|
+
throw new CliError(`Supabase REST row ${index} was not a JSON object for ${url}`, {
|
|
30
|
+
code: 'SUPABASE_REST_RESPONSE_INVALID',
|
|
31
|
+
exitCode: 1,
|
|
32
|
+
details: item,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: typeof item.id === 'string' ? item.id : '',
|
|
37
|
+
version: typeof item.version === 'string' ? item.version : '',
|
|
38
|
+
json: item.json,
|
|
39
|
+
modified_at: typeof item.modified_at === 'string' ? item.modified_at : null,
|
|
40
|
+
state_code: typeof item.state_code === 'number' ? item.state_code : null,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function normalizeSupabaseProcessPayload(payload, lookupKey) {
|
|
45
|
+
if (typeof payload === 'string') {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(payload);
|
|
48
|
+
if (!isRecord(parsed)) {
|
|
49
|
+
throw new CliError(`Supabase REST payload was not a JSON object for ${lookupKey}.`, {
|
|
50
|
+
code: 'SUPABASE_REST_PAYLOAD_INVALID',
|
|
51
|
+
exitCode: 1,
|
|
52
|
+
details: parsed,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof CliError) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
throw new CliError(`Supabase REST payload was not valid JSON for ${lookupKey}.`, {
|
|
62
|
+
code: 'SUPABASE_REST_PAYLOAD_INVALID_JSON',
|
|
63
|
+
exitCode: 1,
|
|
64
|
+
details: String(error),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!isRecord(payload)) {
|
|
69
|
+
throw new CliError(`Supabase REST payload was missing json for ${lookupKey}.`, {
|
|
70
|
+
code: 'SUPABASE_REST_PAYLOAD_MISSING',
|
|
71
|
+
exitCode: 1,
|
|
72
|
+
details: payload,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return payload;
|
|
76
|
+
}
|
|
77
|
+
async function fetchExactOrLatestProcessRow(options) {
|
|
78
|
+
const { client, restBaseUrl } = createSupabaseDataClient(options.runtime, options.fetchImpl, options.timeoutMs);
|
|
79
|
+
if (!options.version) {
|
|
80
|
+
const latestUrl = buildProcessUrl(restBaseUrl, {
|
|
81
|
+
id: options.id,
|
|
82
|
+
latestOnly: true,
|
|
83
|
+
});
|
|
84
|
+
const latestRows = parseRows(await runSupabaseArrayQuery(client
|
|
85
|
+
.from('processes')
|
|
86
|
+
.select('id,version,json,modified_at,state_code')
|
|
87
|
+
.eq('id', options.id)
|
|
88
|
+
.order('version', { ascending: false })
|
|
89
|
+
.limit(1), latestUrl), latestUrl);
|
|
90
|
+
if (latestRows.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
row: latestRows[0],
|
|
95
|
+
sourceUrl: latestUrl,
|
|
96
|
+
resolution: 'remote_supabase_latest',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const exactUrl = buildProcessUrl(restBaseUrl, {
|
|
100
|
+
id: options.id,
|
|
101
|
+
version: options.version,
|
|
102
|
+
});
|
|
103
|
+
const exactRows = parseRows(await runSupabaseArrayQuery(client
|
|
104
|
+
.from('processes')
|
|
105
|
+
.select('id,version,json,modified_at,state_code')
|
|
106
|
+
.eq('id', options.id)
|
|
107
|
+
.eq('version', options.version), exactUrl), exactUrl);
|
|
108
|
+
if (exactRows.length > 0) {
|
|
109
|
+
return {
|
|
110
|
+
row: exactRows[0],
|
|
111
|
+
sourceUrl: exactUrl,
|
|
112
|
+
resolution: 'remote_supabase_exact',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (!options.fallbackToLatest) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const latestUrl = buildProcessUrl(restBaseUrl, {
|
|
119
|
+
id: options.id,
|
|
120
|
+
latestOnly: true,
|
|
121
|
+
});
|
|
122
|
+
const latestRows = parseRows(await runSupabaseArrayQuery(client
|
|
123
|
+
.from('processes')
|
|
124
|
+
.select('id,version,json,modified_at,state_code')
|
|
125
|
+
.eq('id', options.id)
|
|
126
|
+
.order('version', { ascending: false })
|
|
127
|
+
.limit(1), latestUrl), latestUrl);
|
|
128
|
+
if (latestRows.length === 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
row: latestRows[0],
|
|
133
|
+
sourceUrl: latestUrl,
|
|
134
|
+
resolution: 'remote_supabase_latest_fallback',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export { requireSupabaseRestRuntime, deriveSupabaseRestBaseUrl, normalizeSupabaseProcessPayload, fetchExactOrLatestProcessRow, };
|
|
138
|
+
//# sourceMappingURL=supabase-rest.js.map
|