@stardeck-customer-apps/testing 0.19.0 → 0.21.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/SKILL.md +26 -21
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +297 -54
- package/dist/index.mjs +283 -30
- package/dist/setup.js +53 -7
- package/dist/setup.mjs +53 -7
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/test-app.ts
|
|
2
2
|
import crypto8 from "crypto";
|
|
3
|
-
import { readFileSync, existsSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
3
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
4
|
+
import { resolve as resolve2 } from "path";
|
|
5
5
|
|
|
6
6
|
// src/pglite/database.ts
|
|
7
7
|
import { PGlite } from "@electric-sql/pglite";
|
|
@@ -9,10 +9,10 @@ import { citext } from "@electric-sql/pglite/contrib/citext";
|
|
|
9
9
|
import { pg_trgm } from "@electric-sql/pglite/contrib/pg_trgm";
|
|
10
10
|
import { pgcrypto } from "@electric-sql/pglite/contrib/pgcrypto";
|
|
11
11
|
import { uuid_ossp } from "@electric-sql/pglite/contrib/uuid_ossp";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
12
|
+
var EXTENSIONS = { pg_trgm, pgcrypto, uuid_ossp, citext };
|
|
13
|
+
var PGLITE_EXTENSION_NAMES = Object.keys(EXTENSIONS);
|
|
14
|
+
async function createPgliteDatabase(options = {}) {
|
|
15
|
+
const db = new PGlite({ extensions: EXTENSIONS, ...options });
|
|
16
16
|
await db.waitReady;
|
|
17
17
|
return db;
|
|
18
18
|
}
|
|
@@ -33,10 +33,6 @@ async function listPublicTables(db) {
|
|
|
33
33
|
`);
|
|
34
34
|
return rows.map(({ name }) => name);
|
|
35
35
|
}
|
|
36
|
-
async function truncateAllTables(db) {
|
|
37
|
-
const tables = await listPublicTables(db);
|
|
38
|
-
if (tables.length) await db.exec(`TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY`);
|
|
39
|
-
}
|
|
40
36
|
var identityParsers = null;
|
|
41
37
|
function getIdentityParsers() {
|
|
42
38
|
if (!identityParsers) {
|
|
@@ -49,6 +45,208 @@ function getIdentityParsers() {
|
|
|
49
45
|
return identityParsers;
|
|
50
46
|
}
|
|
51
47
|
|
|
48
|
+
// src/pglite/baseline.ts
|
|
49
|
+
var HARNESS_SCHEMA = "stardeck_test";
|
|
50
|
+
var SCHEMA_CHANGES = `${HARNESS_SCHEMA}.schema_changes`;
|
|
51
|
+
var SCHEMA_CHANGE_TRACKING_SQL = `
|
|
52
|
+
CREATE SCHEMA IF NOT EXISTS ${HARNESS_SCHEMA};
|
|
53
|
+
CREATE TABLE IF NOT EXISTS ${SCHEMA_CHANGES} ();
|
|
54
|
+
CREATE OR REPLACE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed() RETURNS event_trigger
|
|
55
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
56
|
+
AS $$ BEGIN INSERT INTO ${SCHEMA_CHANGES} DEFAULT VALUES; END $$;
|
|
57
|
+
DROP EVENT TRIGGER IF EXISTS stardeck_test_ddl;
|
|
58
|
+
DROP EVENT TRIGGER IF EXISTS stardeck_test_drop;
|
|
59
|
+
CREATE EVENT TRIGGER stardeck_test_ddl ON ddl_command_end
|
|
60
|
+
EXECUTE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed();
|
|
61
|
+
CREATE EVENT TRIGGER stardeck_test_drop ON sql_drop
|
|
62
|
+
EXECUTE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed();
|
|
63
|
+
ALTER EVENT TRIGGER stardeck_test_ddl ENABLE ALWAYS;
|
|
64
|
+
ALTER EVENT TRIGGER stardeck_test_drop ENABLE ALWAYS;
|
|
65
|
+
`;
|
|
66
|
+
async function schemaChangedSinceBaseline(db) {
|
|
67
|
+
const { rows } = await db.query(
|
|
68
|
+
`SELECT EXISTS (SELECT FROM ${SCHEMA_CHANGES}) AS changed`
|
|
69
|
+
);
|
|
70
|
+
return rows[0]?.changed === true;
|
|
71
|
+
}
|
|
72
|
+
async function acceptSchemaChanges(db) {
|
|
73
|
+
await db.exec(`DELETE FROM ${SCHEMA_CHANGES};`);
|
|
74
|
+
}
|
|
75
|
+
async function sequenceStates(db) {
|
|
76
|
+
const { rows: sequences } = await db.query(`
|
|
77
|
+
SELECT format('%I.%I', n.nspname, c.relname) AS name
|
|
78
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
79
|
+
WHERE n.nspname = 'public' AND c.relkind = 'S'
|
|
80
|
+
`);
|
|
81
|
+
if (!sequences.length) return null;
|
|
82
|
+
const { rows } = await db.query(
|
|
83
|
+
sequences.map(
|
|
84
|
+
({ name }) => `SELECT ${quoteLiteral(name)} AS name, last_value::text, is_called FROM ${name}`
|
|
85
|
+
).join(" UNION ALL ")
|
|
86
|
+
);
|
|
87
|
+
return `SELECT ${rows.map((s) => `setval(${quoteLiteral(s.name)}, ${s.last_value}, ${s.is_called})`).join(", ")};`;
|
|
88
|
+
}
|
|
89
|
+
function quoteLiteral(value) {
|
|
90
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
91
|
+
}
|
|
92
|
+
async function captureBaseline(db) {
|
|
93
|
+
const { rows: unsilenceable } = await db.query(`
|
|
94
|
+
SELECT FROM pg_trigger t JOIN pg_class c ON c.oid = t.tgrelid
|
|
95
|
+
WHERE c.relnamespace = 'public'::regnamespace AND NOT t.tgisinternal
|
|
96
|
+
AND t.tgenabled IN ('A', 'R')`);
|
|
97
|
+
if (unsilenceable.length) return null;
|
|
98
|
+
const tables = await listPublicTables(db);
|
|
99
|
+
const statements = [];
|
|
100
|
+
await db.exec(`DROP SCHEMA IF EXISTS ${HARNESS_SCHEMA}_baseline CASCADE;
|
|
101
|
+
CREATE SCHEMA ${HARNESS_SCHEMA}_baseline;`);
|
|
102
|
+
if (tables.length) {
|
|
103
|
+
const { rows: populated } = await db.query(
|
|
104
|
+
tables.map(
|
|
105
|
+
(table, index) => `SELECT ${index} AS index, (
|
|
106
|
+
SELECT string_agg(quote_ident(attname), ', ' ORDER BY attnum) FROM pg_attribute
|
|
107
|
+
WHERE attrelid = ${quoteLiteral(table)}::regclass AND attnum > 0
|
|
108
|
+
AND NOT attisdropped AND attgenerated = ''
|
|
109
|
+
) AS columns WHERE EXISTS (SELECT 1 FROM ONLY ${table})`
|
|
110
|
+
).join(" UNION ALL ")
|
|
111
|
+
);
|
|
112
|
+
for (const { index, columns } of populated) {
|
|
113
|
+
const copy = `${HARNESS_SCHEMA}_baseline.t${index}`;
|
|
114
|
+
await db.exec(`CREATE TABLE ${copy} AS SELECT ${columns} FROM ONLY ${tables[index]};`);
|
|
115
|
+
statements.push(
|
|
116
|
+
`INSERT INTO ${tables[index]} (${columns}) OVERRIDING SYSTEM VALUE SELECT ${columns} FROM ${copy};`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const setvals = await sequenceStates(db);
|
|
121
|
+
if (setvals) statements.push(setvals);
|
|
122
|
+
return { restoreSql: statements.join("\n") };
|
|
123
|
+
}
|
|
124
|
+
async function restoreBaseline(db, baseline) {
|
|
125
|
+
const tables = await listPublicTables(db);
|
|
126
|
+
const truncate = tables.length ? `TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY;` : "";
|
|
127
|
+
try {
|
|
128
|
+
await db.exec(`BEGIN; SET LOCAL session_replication_role = replica;
|
|
129
|
+
${truncate}
|
|
130
|
+
SELECT setval(format('%I.%I', schemaname, sequencename), start_value, false)
|
|
131
|
+
FROM pg_sequences WHERE schemaname = 'public';
|
|
132
|
+
${baseline.restoreSql}
|
|
133
|
+
COMMIT;`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
await db.exec("ROLLBACK;").catch(() => {
|
|
136
|
+
});
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/pglite/snapshot.ts
|
|
142
|
+
import { createHash } from "crypto";
|
|
143
|
+
import {
|
|
144
|
+
existsSync,
|
|
145
|
+
mkdirSync,
|
|
146
|
+
readdirSync,
|
|
147
|
+
readFileSync,
|
|
148
|
+
renameSync,
|
|
149
|
+
rmSync,
|
|
150
|
+
statSync,
|
|
151
|
+
utimesSync,
|
|
152
|
+
writeFileSync
|
|
153
|
+
} from "fs";
|
|
154
|
+
import { dirname, join, resolve } from "path";
|
|
155
|
+
var SNAPSHOT_FORMAT = 2;
|
|
156
|
+
var MAX_SNAPSHOTS = 8;
|
|
157
|
+
function snapshotKey(schemaSql) {
|
|
158
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
159
|
+
return createHash("sha256").update(JSON.stringify({ format: SNAPSHOT_FORMAT, extensions: PGLITE_EXTENSION_NAMES, day })).update(SCHEMA_CHANGE_TRACKING_SQL).update(schemaSql).digest("hex").slice(0, 32);
|
|
160
|
+
}
|
|
161
|
+
function snapshotPath(schemaSql) {
|
|
162
|
+
return resolve(
|
|
163
|
+
process.cwd(),
|
|
164
|
+
"node_modules/.cache/stardeck-testing",
|
|
165
|
+
`${snapshotKey(schemaSql)}.tar`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
var CUSTOM_SETTING = /\bset_config\s*\(\s*(?:'([^']*)'|(\$\w*\$)([\s\S]*?)\2)|\bSET\s+(?:SESSION\s+)?(?:"([^"]*)"|([a-z_][\w$]*\.[\w$.]+))/gi;
|
|
169
|
+
async function sessionSettings(db, schemaSql) {
|
|
170
|
+
const custom = [...schemaSql.matchAll(CUSTOM_SETTING)].map(
|
|
171
|
+
([, quoted, , dollarQuoted, identifier, bare]) => quoted ?? dollarQuoted ?? identifier ?? bare
|
|
172
|
+
).filter((name) => name?.includes("."));
|
|
173
|
+
const { rows } = await db.query(
|
|
174
|
+
`SELECT string_agg(name || '=' || setting, ',' ORDER BY name) AS settings FROM (
|
|
175
|
+
SELECT name, setting FROM pg_settings WHERE source = 'session'
|
|
176
|
+
UNION ALL
|
|
177
|
+
SELECT name, current_setting(name, true) FROM unnest($1::text[]) AS name
|
|
178
|
+
WHERE current_setting(name, true) IS NOT NULL
|
|
179
|
+
) settings`,
|
|
180
|
+
[custom]
|
|
181
|
+
);
|
|
182
|
+
return rows[0]?.settings ?? "";
|
|
183
|
+
}
|
|
184
|
+
async function buildSchemaDatabase(schemaSql) {
|
|
185
|
+
const db = await createPgliteDatabase();
|
|
186
|
+
try {
|
|
187
|
+
await db.exec(SCHEMA_CHANGE_TRACKING_SQL);
|
|
188
|
+
const settingsBefore = await sessionSettings(db, schemaSql);
|
|
189
|
+
await applySchema(db, schemaSql);
|
|
190
|
+
const { rows: sessionBound } = await db.query(
|
|
191
|
+
`SELECT FROM pg_class WHERE relpersistence IN ('u', 't')`
|
|
192
|
+
);
|
|
193
|
+
const cacheable = !sessionBound.length && await sessionSettings(db, schemaSql) === settingsBefore;
|
|
194
|
+
await db.exec(`COMMENT ON SCHEMA stardeck_test IS '${snapshotKey(schemaSql)}'`);
|
|
195
|
+
await db.exec("CHECKPOINT");
|
|
196
|
+
return { db, cacheable };
|
|
197
|
+
} catch (error) {
|
|
198
|
+
await db.close().catch(() => {
|
|
199
|
+
});
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function pruneSnapshots(dir) {
|
|
204
|
+
const snapshots = readdirSync(dir).filter((name) => name.endsWith(".tar")).map((name) => ({ path: join(dir, name), mtime: statSync(join(dir, name)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
205
|
+
for (const { path } of snapshots.slice(MAX_SNAPSHOTS)) rmSync(path, { force: true });
|
|
206
|
+
}
|
|
207
|
+
async function writeSnapshot(path, db) {
|
|
208
|
+
try {
|
|
209
|
+
const data = Buffer.from(await (await db.dumpDataDir("none")).arrayBuffer());
|
|
210
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
211
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
212
|
+
writeFileSync(tmp, data);
|
|
213
|
+
renameSync(tmp, path);
|
|
214
|
+
pruneSnapshots(dirname(path));
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.warn(`[stardeck-testing] could not cache schema snapshot at ${path}:`, error);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function loadSnapshot(path, key) {
|
|
220
|
+
let db = null;
|
|
221
|
+
try {
|
|
222
|
+
db = await createPgliteDatabase({ loadDataDir: new Blob([readFileSync(path)]) });
|
|
223
|
+
const { rows } = await db.query(
|
|
224
|
+
`SELECT obj_description(to_regnamespace('stardeck_test'), 'pg_namespace') AS key`
|
|
225
|
+
);
|
|
226
|
+
if (rows[0]?.key === key) {
|
|
227
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
228
|
+
utimesSync(path, now3, now3);
|
|
229
|
+
return db;
|
|
230
|
+
}
|
|
231
|
+
console.warn(`[stardeck-testing] rebuilding schema snapshot ${path}: key mismatch`);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(`[stardeck-testing] rebuilding unreadable schema snapshot ${path}:`, error);
|
|
234
|
+
}
|
|
235
|
+
await db?.close().catch(() => {
|
|
236
|
+
});
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async function bootWithSchema(schemaSql) {
|
|
240
|
+
const path = snapshotPath(schemaSql);
|
|
241
|
+
if (existsSync(path)) {
|
|
242
|
+
const loaded = await loadSnapshot(path, snapshotKey(schemaSql));
|
|
243
|
+
if (loaded) return loaded;
|
|
244
|
+
}
|
|
245
|
+
const { db, cacheable } = await buildSchemaDatabase(schemaSql);
|
|
246
|
+
if (cacheable) await writeSnapshot(path, db);
|
|
247
|
+
return db;
|
|
248
|
+
}
|
|
249
|
+
|
|
52
250
|
// src/global-singleton.ts
|
|
53
251
|
function globalSingleton(key, create) {
|
|
54
252
|
const symbol = /* @__PURE__ */ Symbol.for(`stardeck-testing.${key}`);
|
|
@@ -2865,6 +3063,7 @@ function createEdge() {
|
|
|
2865
3063
|
}
|
|
2866
3064
|
|
|
2867
3065
|
// src/simulator/starlens.ts
|
|
3066
|
+
var LATEST_VERSION = "v2";
|
|
2868
3067
|
function starlensFailure(error, status, code) {
|
|
2869
3068
|
return json({ success: false, error, code }, status);
|
|
2870
3069
|
}
|
|
@@ -2883,11 +3082,18 @@ function nullFlagged(output, issues) {
|
|
|
2883
3082
|
}
|
|
2884
3083
|
return copy;
|
|
2885
3084
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
3085
|
+
function isInputShape(input) {
|
|
3086
|
+
if (!input || typeof input !== "object") return false;
|
|
3087
|
+
const i = input;
|
|
3088
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
3089
|
+
}
|
|
3090
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
3091
|
+
function isCroppable(input) {
|
|
3092
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
3093
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
3094
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
3095
|
+
}
|
|
3096
|
+
function validateStarlensBody(body) {
|
|
2891
3097
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2892
3098
|
if (inputs.length === 0) {
|
|
2893
3099
|
return starlensFailure(
|
|
@@ -2896,6 +3102,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2896
3102
|
"VALIDATION_ERROR"
|
|
2897
3103
|
);
|
|
2898
3104
|
}
|
|
3105
|
+
if (!inputs.every(isInputShape)) {
|
|
3106
|
+
return starlensFailure(
|
|
3107
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
3108
|
+
400,
|
|
3109
|
+
"VALIDATION_ERROR"
|
|
3110
|
+
);
|
|
3111
|
+
}
|
|
2899
3112
|
const schema = body.schema;
|
|
2900
3113
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2901
3114
|
return starlensFailure(
|
|
@@ -2904,8 +3117,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2904
3117
|
"VALIDATION_ERROR"
|
|
2905
3118
|
);
|
|
2906
3119
|
}
|
|
3120
|
+
const version = body.version ?? "latest";
|
|
3121
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
3122
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
3123
|
+
}
|
|
3124
|
+
const effort = body.effort ?? "low";
|
|
3125
|
+
if (effort !== "low" && effort !== "high") {
|
|
3126
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
3127
|
+
}
|
|
3128
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
3129
|
+
if (effort === "high" && resolved === "v1") {
|
|
3130
|
+
return starlensFailure(
|
|
3131
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
3132
|
+
400,
|
|
3133
|
+
"unsupported_effort"
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
return { version, resolved, effort, inputs, schema };
|
|
3137
|
+
}
|
|
3138
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
3139
|
+
const body = await readJsonBody(request);
|
|
3140
|
+
const valid = validateStarlensBody(body);
|
|
3141
|
+
if (valid instanceof Response) return valid;
|
|
3142
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
3143
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
3144
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
3145
|
+
}
|
|
2907
3146
|
const call = {
|
|
2908
|
-
version
|
|
3147
|
+
version,
|
|
3148
|
+
effort,
|
|
2909
3149
|
inputs: inputs.map((i) => ({
|
|
2910
3150
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2911
3151
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2927,8 +3167,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2927
3167
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2928
3168
|
}
|
|
2929
3169
|
const issues = reply.issues ?? [];
|
|
3170
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2930
3171
|
return success({
|
|
2931
|
-
version:
|
|
3172
|
+
version: resolved,
|
|
3173
|
+
effort: ran,
|
|
2932
3174
|
output: nullFlagged(reply.output, issues),
|
|
2933
3175
|
issues,
|
|
2934
3176
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
@@ -3198,8 +3440,8 @@ function buildUser(partial) {
|
|
|
3198
3440
|
function loadSchemaSql(options) {
|
|
3199
3441
|
if (options.schemaSql !== void 0) return options.schemaSql;
|
|
3200
3442
|
if (options.schema === null) return null;
|
|
3201
|
-
const schemaPath =
|
|
3202
|
-
if (!
|
|
3443
|
+
const schemaPath = resolve2(process.cwd(), options.schema ?? DEFAULT_SCHEMA_PATH);
|
|
3444
|
+
if (!existsSync2(schemaPath)) {
|
|
3203
3445
|
if (options.schema) {
|
|
3204
3446
|
throw new Error(
|
|
3205
3447
|
`[stardeck-testing] Schema snapshot not found at ${schemaPath}. Run \`npx stardeck-data-store generate-types\` to regenerate it.`
|
|
@@ -3207,7 +3449,7 @@ function loadSchemaSql(options) {
|
|
|
3207
3449
|
}
|
|
3208
3450
|
return null;
|
|
3209
3451
|
}
|
|
3210
|
-
return
|
|
3452
|
+
return readFileSync2(schemaPath, "utf-8");
|
|
3211
3453
|
}
|
|
3212
3454
|
async function createTestApp(options = {}) {
|
|
3213
3455
|
if (state.db) {
|
|
@@ -3218,15 +3460,23 @@ async function createTestApp(options = {}) {
|
|
|
3218
3460
|
applyTestEnv(options);
|
|
3219
3461
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
3220
3462
|
installFetchRouter();
|
|
3221
|
-
const
|
|
3463
|
+
const schemaSql = loadSchemaSql(options) ?? "";
|
|
3464
|
+
const db = await bootWithSchema(schemaSql);
|
|
3222
3465
|
state.db = db;
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
if (schemaSql) await applySchema(db, schemaSql);
|
|
3466
|
+
let baseline;
|
|
3467
|
+
const seed = async () => {
|
|
3226
3468
|
if (options.seed) await options.seed(db);
|
|
3469
|
+
await acceptSchemaChanges(db);
|
|
3470
|
+
};
|
|
3471
|
+
const rebuild = async () => {
|
|
3472
|
+
await dropAllTables(db);
|
|
3473
|
+
await applySchema(db, schemaSql);
|
|
3474
|
+
baseline = await captureBaseline(db);
|
|
3475
|
+
await seed();
|
|
3227
3476
|
};
|
|
3228
3477
|
try {
|
|
3229
|
-
await
|
|
3478
|
+
baseline = await captureBaseline(db);
|
|
3479
|
+
await seed();
|
|
3230
3480
|
} catch (error) {
|
|
3231
3481
|
state.db = null;
|
|
3232
3482
|
await db.close().catch(() => {
|
|
@@ -3272,12 +3522,15 @@ async function createTestApp(options = {}) {
|
|
|
3272
3522
|
return tokens;
|
|
3273
3523
|
},
|
|
3274
3524
|
async reset(resetOptions) {
|
|
3275
|
-
if (resetOptions?.mode === "
|
|
3276
|
-
await
|
|
3277
|
-
if (options.seed) await options.seed(db);
|
|
3525
|
+
if (resetOptions?.mode === "schema" || !baseline || await schemaChangedSinceBaseline(db)) {
|
|
3526
|
+
await rebuild();
|
|
3278
3527
|
} else {
|
|
3279
|
-
await
|
|
3280
|
-
|
|
3528
|
+
await restoreBaseline(db, baseline);
|
|
3529
|
+
try {
|
|
3530
|
+
await seed();
|
|
3531
|
+
} catch {
|
|
3532
|
+
await rebuild();
|
|
3533
|
+
}
|
|
3281
3534
|
}
|
|
3282
3535
|
state.currentUser = null;
|
|
3283
3536
|
state.sessions.clear();
|
package/dist/setup.js
CHANGED
|
@@ -504,6 +504,8 @@ var import_citext = require("@electric-sql/pglite/contrib/citext");
|
|
|
504
504
|
var import_pg_trgm = require("@electric-sql/pglite/contrib/pg_trgm");
|
|
505
505
|
var import_pgcrypto = require("@electric-sql/pglite/contrib/pgcrypto");
|
|
506
506
|
var import_uuid_ossp = require("@electric-sql/pglite/contrib/uuid_ossp");
|
|
507
|
+
var EXTENSIONS = { pg_trgm: import_pg_trgm.pg_trgm, pgcrypto: import_pgcrypto.pgcrypto, uuid_ossp: import_uuid_ossp.uuid_ossp, citext: import_citext.citext };
|
|
508
|
+
var PGLITE_EXTENSION_NAMES = Object.keys(EXTENSIONS);
|
|
507
509
|
var identityParsers = null;
|
|
508
510
|
function getIdentityParsers() {
|
|
509
511
|
if (!identityParsers) {
|
|
@@ -2245,6 +2247,7 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2245
2247
|
}
|
|
2246
2248
|
|
|
2247
2249
|
// src/simulator/starlens.ts
|
|
2250
|
+
var LATEST_VERSION = "v2";
|
|
2248
2251
|
function starlensFailure(error, status, code) {
|
|
2249
2252
|
return json({ success: false, error, code }, status);
|
|
2250
2253
|
}
|
|
@@ -2263,11 +2266,18 @@ function nullFlagged(output, issues) {
|
|
|
2263
2266
|
}
|
|
2264
2267
|
return copy;
|
|
2265
2268
|
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2269
|
+
function isInputShape(input) {
|
|
2270
|
+
if (!input || typeof input !== "object") return false;
|
|
2271
|
+
const i = input;
|
|
2272
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
2273
|
+
}
|
|
2274
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
2275
|
+
function isCroppable(input) {
|
|
2276
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
2277
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
2278
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
2279
|
+
}
|
|
2280
|
+
function validateStarlensBody(body) {
|
|
2271
2281
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2272
2282
|
if (inputs.length === 0) {
|
|
2273
2283
|
return starlensFailure(
|
|
@@ -2276,6 +2286,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2276
2286
|
"VALIDATION_ERROR"
|
|
2277
2287
|
);
|
|
2278
2288
|
}
|
|
2289
|
+
if (!inputs.every(isInputShape)) {
|
|
2290
|
+
return starlensFailure(
|
|
2291
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
2292
|
+
400,
|
|
2293
|
+
"VALIDATION_ERROR"
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2279
2296
|
const schema = body.schema;
|
|
2280
2297
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2281
2298
|
return starlensFailure(
|
|
@@ -2284,8 +2301,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2284
2301
|
"VALIDATION_ERROR"
|
|
2285
2302
|
);
|
|
2286
2303
|
}
|
|
2304
|
+
const version = body.version ?? "latest";
|
|
2305
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
2306
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
2307
|
+
}
|
|
2308
|
+
const effort = body.effort ?? "low";
|
|
2309
|
+
if (effort !== "low" && effort !== "high") {
|
|
2310
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
2311
|
+
}
|
|
2312
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
2313
|
+
if (effort === "high" && resolved === "v1") {
|
|
2314
|
+
return starlensFailure(
|
|
2315
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
2316
|
+
400,
|
|
2317
|
+
"unsupported_effort"
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
return { version, resolved, effort, inputs, schema };
|
|
2321
|
+
}
|
|
2322
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2323
|
+
const body = await readJsonBody(request);
|
|
2324
|
+
const valid = validateStarlensBody(body);
|
|
2325
|
+
if (valid instanceof Response) return valid;
|
|
2326
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
2327
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2328
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2329
|
+
}
|
|
2287
2330
|
const call = {
|
|
2288
|
-
version
|
|
2331
|
+
version,
|
|
2332
|
+
effort,
|
|
2289
2333
|
inputs: inputs.map((i) => ({
|
|
2290
2334
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2291
2335
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2307,8 +2351,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2307
2351
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2308
2352
|
}
|
|
2309
2353
|
const issues = reply.issues ?? [];
|
|
2354
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2310
2355
|
return success({
|
|
2311
|
-
version:
|
|
2356
|
+
version: resolved,
|
|
2357
|
+
effort: ran,
|
|
2312
2358
|
output: nullFlagged(reply.output, issues),
|
|
2313
2359
|
issues,
|
|
2314
2360
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
package/dist/setup.mjs
CHANGED
|
@@ -480,6 +480,8 @@ import { citext } from "@electric-sql/pglite/contrib/citext";
|
|
|
480
480
|
import { pg_trgm } from "@electric-sql/pglite/contrib/pg_trgm";
|
|
481
481
|
import { pgcrypto } from "@electric-sql/pglite/contrib/pgcrypto";
|
|
482
482
|
import { uuid_ossp } from "@electric-sql/pglite/contrib/uuid_ossp";
|
|
483
|
+
var EXTENSIONS = { pg_trgm, pgcrypto, uuid_ossp, citext };
|
|
484
|
+
var PGLITE_EXTENSION_NAMES = Object.keys(EXTENSIONS);
|
|
483
485
|
var identityParsers = null;
|
|
484
486
|
function getIdentityParsers() {
|
|
485
487
|
if (!identityParsers) {
|
|
@@ -2221,6 +2223,7 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2221
2223
|
}
|
|
2222
2224
|
|
|
2223
2225
|
// src/simulator/starlens.ts
|
|
2226
|
+
var LATEST_VERSION = "v2";
|
|
2224
2227
|
function starlensFailure(error, status, code) {
|
|
2225
2228
|
return json({ success: false, error, code }, status);
|
|
2226
2229
|
}
|
|
@@ -2239,11 +2242,18 @@ function nullFlagged(output, issues) {
|
|
|
2239
2242
|
}
|
|
2240
2243
|
return copy;
|
|
2241
2244
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2245
|
+
function isInputShape(input) {
|
|
2246
|
+
if (!input || typeof input !== "object") return false;
|
|
2247
|
+
const i = input;
|
|
2248
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
2249
|
+
}
|
|
2250
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
2251
|
+
function isCroppable(input) {
|
|
2252
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
2253
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
2254
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
2255
|
+
}
|
|
2256
|
+
function validateStarlensBody(body) {
|
|
2247
2257
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2248
2258
|
if (inputs.length === 0) {
|
|
2249
2259
|
return starlensFailure(
|
|
@@ -2252,6 +2262,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2252
2262
|
"VALIDATION_ERROR"
|
|
2253
2263
|
);
|
|
2254
2264
|
}
|
|
2265
|
+
if (!inputs.every(isInputShape)) {
|
|
2266
|
+
return starlensFailure(
|
|
2267
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
2268
|
+
400,
|
|
2269
|
+
"VALIDATION_ERROR"
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2255
2272
|
const schema = body.schema;
|
|
2256
2273
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2257
2274
|
return starlensFailure(
|
|
@@ -2260,8 +2277,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2260
2277
|
"VALIDATION_ERROR"
|
|
2261
2278
|
);
|
|
2262
2279
|
}
|
|
2280
|
+
const version = body.version ?? "latest";
|
|
2281
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
2282
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
2283
|
+
}
|
|
2284
|
+
const effort = body.effort ?? "low";
|
|
2285
|
+
if (effort !== "low" && effort !== "high") {
|
|
2286
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
2287
|
+
}
|
|
2288
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
2289
|
+
if (effort === "high" && resolved === "v1") {
|
|
2290
|
+
return starlensFailure(
|
|
2291
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
2292
|
+
400,
|
|
2293
|
+
"unsupported_effort"
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
return { version, resolved, effort, inputs, schema };
|
|
2297
|
+
}
|
|
2298
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2299
|
+
const body = await readJsonBody(request);
|
|
2300
|
+
const valid = validateStarlensBody(body);
|
|
2301
|
+
if (valid instanceof Response) return valid;
|
|
2302
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
2303
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2304
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2305
|
+
}
|
|
2263
2306
|
const call = {
|
|
2264
|
-
version
|
|
2307
|
+
version,
|
|
2308
|
+
effort,
|
|
2265
2309
|
inputs: inputs.map((i) => ({
|
|
2266
2310
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2267
2311
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2283,8 +2327,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2283
2327
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2284
2328
|
}
|
|
2285
2329
|
const issues = reply.issues ?? [];
|
|
2330
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2286
2331
|
return success({
|
|
2287
|
-
version:
|
|
2332
|
+
version: resolved,
|
|
2333
|
+
effort: ran,
|
|
2288
2334
|
output: nullFlagged(reply.output, issues),
|
|
2289
2335
|
issues,
|
|
2290
2336
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|