@fuzdev/fuz_app 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/action_codegen.d.ts +25 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +39 -0
- package/dist/actions/action_peer.d.ts +7 -0
- package/dist/actions/action_peer.d.ts.map +1 -1
- package/dist/actions/action_peer.js +1 -1
- package/dist/actions/action_types.d.ts +16 -1
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/cancel.d.ts +78 -0
- package/dist/actions/cancel.d.ts.map +1 -0
- package/dist/actions/cancel.js +79 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +62 -22
- package/dist/actions/rpc_client.d.ts +10 -0
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +22 -7
- package/dist/actions/socket.svelte.d.ts +22 -10
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +46 -12
- package/dist/actions/transports.d.ts +14 -3
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports_http.d.ts +3 -3
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +4 -3
- package/dist/actions/transports_ws.d.ts +33 -6
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +43 -46
- package/dist/actions/transports_ws_backend.d.ts +12 -3
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +12 -1
- package/dist/auth/bearer_auth.js +0 -1
- package/dist/auth/keyring.d.ts.map +1 -1
- package/dist/auth/keyring.js +0 -2
- package/dist/auth/migrations.js +4 -4
- package/dist/db/migrate.d.ts +12 -2
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +25 -16
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +0 -2
- package/dist/dev/setup.d.ts +34 -0
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +50 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +0 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +0 -3
- package/dist/testing/app_server.js +1 -1
- package/dist/testing/data_exposure.js +6 -8
- package/dist/testing/db.js +1 -1
- package/dist/testing/integration.js +0 -1
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +0 -6
- package/dist/testing/rpc_round_trip.js +4 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -2
- package/package.json +2 -2
package/dist/db/migrate.js
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Migrations are functions in ordered arrays, grouped by namespace.
|
|
5
5
|
* A `schema_version` table tracks progress per namespace.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* **Chain-level transactions**: All pending migrations in a namespace run in a
|
|
8
|
+
* single transaction. Any failure rolls back every migration in that run —
|
|
9
|
+
* no partial-state recovery. This rules out non-transactional DDL (e.g.,
|
|
10
|
+
* `CREATE INDEX CONCURRENTLY`); run those out of band.
|
|
7
11
|
*
|
|
8
12
|
* **Forward-only**: No down-migrations. Schema changes are additive.
|
|
9
13
|
* For pre-release development, collapse migrations into a single v0.
|
|
@@ -46,9 +50,15 @@ const namespace_lock_key = (namespace) => {
|
|
|
46
50
|
*
|
|
47
51
|
* Creates the `schema_version` tracking table if it does not exist,
|
|
48
52
|
* then for each namespace: acquires an advisory lock, reads the current
|
|
49
|
-
* version, runs pending migrations in order
|
|
53
|
+
* version, runs all pending migrations in order inside a single transaction,
|
|
50
54
|
* updates the stored version, and releases the lock.
|
|
51
55
|
*
|
|
56
|
+
* **Atomicity**: The pending chain for each namespace runs in one transaction —
|
|
57
|
+
* any failure rolls back every migration that ran in that invocation. The
|
|
58
|
+
* next run starts from the previously-stored version, re-running the whole
|
|
59
|
+
* (fixed) chain. Namespaces are independent: a later namespace's failure
|
|
60
|
+
* does not roll back an earlier namespace that already committed.
|
|
61
|
+
*
|
|
52
62
|
* **Concurrency**: Uses PostgreSQL advisory locks to serialize concurrent
|
|
53
63
|
* callers on the same namespace. Safe for multi-instance deployments.
|
|
54
64
|
*
|
|
@@ -59,7 +69,6 @@ const namespace_lock_key = (namespace) => {
|
|
|
59
69
|
export const run_migrations = async (db, namespaces) => {
|
|
60
70
|
await db.query(SCHEMA_VERSION_DDL);
|
|
61
71
|
const results = [];
|
|
62
|
-
/* eslint-disable no-await-in-loop */
|
|
63
72
|
for (const { namespace, migrations } of namespaces) {
|
|
64
73
|
const lock_key = namespace_lock_key(namespace);
|
|
65
74
|
// Acquire advisory lock — serializes concurrent migration runs
|
|
@@ -78,24 +87,25 @@ export const run_migrations = async (db, namespaces) => {
|
|
|
78
87
|
if (current_version === migrations.length) {
|
|
79
88
|
continue; // up to date
|
|
80
89
|
}
|
|
81
|
-
// run pending migrations
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
// run all pending migrations in a single transaction — any failure
|
|
91
|
+
// rolls back the whole pending chain
|
|
92
|
+
await db.transaction(async (tx) => {
|
|
93
|
+
for (let i = current_version; i < migrations.length; i++) {
|
|
94
|
+
const { fn, name } = resolve_migration(migrations[i]);
|
|
95
|
+
const label = name != null ? `"${name}"` : '';
|
|
96
|
+
try {
|
|
87
97
|
await fn(tx);
|
|
88
98
|
await tx.query(`INSERT INTO schema_version (namespace, version, applied_at)
|
|
89
99
|
VALUES ($1, $2, NOW())
|
|
90
100
|
ON CONFLICT (namespace)
|
|
91
101
|
DO UPDATE SET version = $2, applied_at = NOW()`, [namespace, i + 1]);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const name_part = label ? ` ${label}` : '';
|
|
105
|
+
throw new Error(`Migration ${namespace}[${i}]${name_part} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
|
-
}
|
|
108
|
+
});
|
|
99
109
|
results.push({
|
|
100
110
|
namespace,
|
|
101
111
|
from_version: current_version,
|
|
@@ -113,6 +123,5 @@ export const run_migrations = async (db, namespaces) => {
|
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
|
-
/* eslint-enable no-await-in-loop */
|
|
117
126
|
return results;
|
|
118
127
|
};
|
package/dist/db/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA8ElB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MA6BnD,CAAC"}
|
package/dist/db/status.js
CHANGED
|
@@ -36,7 +36,6 @@ export const query_db_status = async (db, namespaces) => {
|
|
|
36
36
|
const tables = [];
|
|
37
37
|
for (const { table_name } of table_rows) {
|
|
38
38
|
// table_name from information_schema is trusted
|
|
39
|
-
// eslint-disable-next-line no-await-in-loop
|
|
40
39
|
const result = await db.query_one(`SELECT COUNT(*) as count FROM "${table_name}"`);
|
|
41
40
|
tables.push({
|
|
42
41
|
name: table_name,
|
|
@@ -53,7 +52,6 @@ export const query_db_status = async (db, namespaces) => {
|
|
|
53
52
|
) as exists`);
|
|
54
53
|
if (sv_exists?.exists) {
|
|
55
54
|
for (const { namespace, migrations: ns_migrations } of namespaces) {
|
|
56
|
-
// eslint-disable-next-line no-await-in-loop
|
|
57
55
|
const row = await db.query_one('SELECT version FROM schema_version WHERE namespace = $1', [namespace]);
|
|
58
56
|
const current_version = row?.version ?? 0;
|
|
59
57
|
migrations.push({
|
package/dist/dev/setup.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
11
|
import type { CommandDeps, CommandResult, EnvDeps, FsReadDeps, FsRemoveDeps, FsWriteDeps } from '../runtime/deps.js';
|
|
12
|
+
import type { QueryDeps } from '../db/query_deps.js';
|
|
12
13
|
/**
|
|
13
14
|
* Optional logger for setup helpers.
|
|
14
15
|
*
|
|
@@ -156,4 +157,37 @@ export declare const create_database: (deps: CommandDeps, db_name: string, optio
|
|
|
156
157
|
* @returns result describing what happened
|
|
157
158
|
*/
|
|
158
159
|
export declare const reset_database: (deps: CommandDeps & FsReadDeps & FsRemoveDeps, database_url: string, options?: ResetDatabaseOptions) => Promise<ResetDbResult>;
|
|
160
|
+
/** Input to `seed_dev_account`. */
|
|
161
|
+
export interface SeedDevAccountInput {
|
|
162
|
+
/** Account username. Policy is bypassed — any non-empty string is accepted. */
|
|
163
|
+
username: string;
|
|
164
|
+
/** Account password. Policy is bypassed — any non-empty string is accepted. */
|
|
165
|
+
password: string;
|
|
166
|
+
/** Roles to grant via permit (idempotent). */
|
|
167
|
+
roles?: ReadonlyArray<string>;
|
|
168
|
+
}
|
|
169
|
+
/** Result of `seed_dev_account`. */
|
|
170
|
+
export interface SeedDevAccountResult {
|
|
171
|
+
account_id: string;
|
|
172
|
+
actor_id: string;
|
|
173
|
+
/** True if a new account was created; false if one already existed. */
|
|
174
|
+
created: boolean;
|
|
175
|
+
}
|
|
176
|
+
/** Dependencies for `seed_dev_account`. */
|
|
177
|
+
export interface SeedDevAccountDeps extends QueryDeps {
|
|
178
|
+
/** Password hasher (e.g., `argon2_password_deps.hash_password`). */
|
|
179
|
+
hash_password: (password: string) => Promise<string>;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Seed a development test account, bypassing username/password policy.
|
|
183
|
+
*
|
|
184
|
+
* Idempotent by username — if an account with the given username already
|
|
185
|
+
* exists, reuses it and only reconciles the requested role grants. Never
|
|
186
|
+
* updates an existing password (rerun would silently rotate it).
|
|
187
|
+
*
|
|
188
|
+
* Intended for `scripts/dev_setup.ts` — do not call in production.
|
|
189
|
+
*/
|
|
190
|
+
export declare const seed_dev_account: (deps: SeedDevAccountDeps, input: SeedDevAccountInput, options?: {
|
|
191
|
+
log?: SetupLogger;
|
|
192
|
+
}) => Promise<SeedDevAccountResult>;
|
|
159
193
|
//# sourceMappingURL=setup.d.ts.map
|
package/dist/dev/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/dev/setup.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
|
+
import { query_account_by_username, query_actor_by_account, query_create_account_with_actor, } from '../auth/account_queries.js';
|
|
12
|
+
import { query_grant_permit } from '../auth/permit_queries.js';
|
|
11
13
|
/** Default logger using bracket format. */
|
|
12
14
|
export const default_setup_logger = {
|
|
13
15
|
ok: (msg) => console.log(` [ok] ${msg}`),
|
|
@@ -93,7 +95,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
|
|
|
93
95
|
for (const [key, generate] of Object.entries(replacements)) {
|
|
94
96
|
const pattern = new RegExp(`^${key}=$`, 'm');
|
|
95
97
|
if (pattern.test(content)) {
|
|
96
|
-
const value = await generate();
|
|
98
|
+
const value = await generate();
|
|
97
99
|
content = content.replace(pattern, `${key}=${value}`);
|
|
98
100
|
changed = true;
|
|
99
101
|
log.ok(`Generated ${key} in existing ${env_path}`);
|
|
@@ -114,7 +116,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
|
|
|
114
116
|
for (const [key, generate] of Object.entries(replacements)) {
|
|
115
117
|
const pattern = new RegExp(`^${key}=$`, 'm');
|
|
116
118
|
if (pattern.test(content)) {
|
|
117
|
-
const value = await generate();
|
|
119
|
+
const value = await generate();
|
|
118
120
|
content = content.replace(pattern, `${key}=${value}`);
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -263,3 +265,49 @@ export const reset_database = async (deps, database_url, options) => {
|
|
|
263
265
|
log.ok(`Created database: ${db_name}`);
|
|
264
266
|
return { reset: true, skipped: false, db_type: 'postgres' };
|
|
265
267
|
};
|
|
268
|
+
/**
|
|
269
|
+
* Seed a development test account, bypassing username/password policy.
|
|
270
|
+
*
|
|
271
|
+
* Idempotent by username — if an account with the given username already
|
|
272
|
+
* exists, reuses it and only reconciles the requested role grants. Never
|
|
273
|
+
* updates an existing password (rerun would silently rotate it).
|
|
274
|
+
*
|
|
275
|
+
* Intended for `scripts/dev_setup.ts` — do not call in production.
|
|
276
|
+
*/
|
|
277
|
+
export const seed_dev_account = async (deps, input, options) => {
|
|
278
|
+
const log = options?.log ?? default_setup_logger;
|
|
279
|
+
const query_deps = { db: deps.db };
|
|
280
|
+
const existing = await query_account_by_username(query_deps, input.username);
|
|
281
|
+
if (existing) {
|
|
282
|
+
const actor = await query_actor_by_account(query_deps, existing.id);
|
|
283
|
+
if (!actor) {
|
|
284
|
+
log.error(`dev account '${input.username}' exists but has no actor`);
|
|
285
|
+
throw new Error(`dev account '${input.username}' has no actor`);
|
|
286
|
+
}
|
|
287
|
+
for (const role of input.roles ?? []) {
|
|
288
|
+
await query_grant_permit(query_deps, {
|
|
289
|
+
actor_id: actor.id,
|
|
290
|
+
role,
|
|
291
|
+
granted_by: null,
|
|
292
|
+
expires_at: null,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
log.skip(`Dev account '${input.username}' already exists`);
|
|
296
|
+
return { account_id: existing.id, actor_id: actor.id, created: false };
|
|
297
|
+
}
|
|
298
|
+
const password_hash = await deps.hash_password(input.password);
|
|
299
|
+
const { account, actor } = await query_create_account_with_actor(query_deps, {
|
|
300
|
+
username: input.username,
|
|
301
|
+
password_hash,
|
|
302
|
+
});
|
|
303
|
+
for (const role of input.roles ?? []) {
|
|
304
|
+
await query_grant_permit(query_deps, {
|
|
305
|
+
actor_id: actor.id,
|
|
306
|
+
role,
|
|
307
|
+
granted_by: null,
|
|
308
|
+
expires_at: null,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
log.ok(`Seeded dev account '${input.username}'`);
|
|
312
|
+
return { account_id: account.id, actor_id: actor.id, created: true };
|
|
313
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CAsN9E,CAAC"}
|
package/dist/http/db_routes.js
CHANGED
|
@@ -66,7 +66,6 @@ export const create_db_route_specs = (options) => {
|
|
|
66
66
|
ORDER BY table_name`);
|
|
67
67
|
const tables = [];
|
|
68
68
|
for (const { table_name } of table_names) {
|
|
69
|
-
// eslint-disable-next-line no-await-in-loop
|
|
70
69
|
const result = await route.db.query_one(`SELECT COUNT(*) as count FROM "${assert_valid_sql_identifier(table_name)}"`);
|
|
71
70
|
tables.push({
|
|
72
71
|
name: table_name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAUjB;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAgDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,
|
|
1
|
+
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAUjB;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAgDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAsnCF,CAAC"}
|
|
@@ -804,7 +804,6 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
804
804
|
const admin_routes = test_app.route_specs.filter((s) => s.path.startsWith(prefix) && s.auth.type === 'role' && s.auth.role === 'admin');
|
|
805
805
|
// Hit admin routes without auth to exercise 401 error schemas
|
|
806
806
|
for (const route of admin_routes.slice(0, 5)) {
|
|
807
|
-
// eslint-disable-next-line no-await-in-loop
|
|
808
807
|
const res = await test_app.app.request(route.path, {
|
|
809
808
|
method: route.method,
|
|
810
809
|
headers: { host: 'localhost' },
|
|
@@ -826,12 +825,10 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
826
825
|
s.auth.role === 'admin');
|
|
827
826
|
assert.ok(admin_get_routes.length > 0, 'Expected at least one admin GET route — ensure create_route_specs includes admin routes');
|
|
828
827
|
for (const route of admin_get_routes) {
|
|
829
|
-
// eslint-disable-next-line no-await-in-loop
|
|
830
828
|
const res = await test_app.app.request(route.path, {
|
|
831
829
|
headers: test_app.create_session_headers(),
|
|
832
830
|
});
|
|
833
831
|
assert.strictEqual(res.status, 200, `${route.method} ${route.path} should return 200`);
|
|
834
|
-
// eslint-disable-next-line no-await-in-loop
|
|
835
832
|
await assert_response_matches_spec(test_app.route_specs, route.method, route.path, res);
|
|
836
833
|
}
|
|
837
834
|
});
|
|
@@ -51,7 +51,7 @@ export const bootstrap_test_account = async (options) => {
|
|
|
51
51
|
});
|
|
52
52
|
// Grant roles
|
|
53
53
|
for (const role of roles) {
|
|
54
|
-
await query_grant_permit(deps, { actor_id: actor.id, role, granted_by: null });
|
|
54
|
+
await query_grant_permit(deps, { actor_id: actor.id, role, granted_by: null });
|
|
55
55
|
}
|
|
56
56
|
// Create API token
|
|
57
57
|
const { token: api_token, id: token_id, token_hash } = generate_api_token();
|
|
@@ -171,18 +171,17 @@ const describe_data_exposure_runtime_tests = (options) => {
|
|
|
171
171
|
if (skip_set.has(route_key))
|
|
172
172
|
continue;
|
|
173
173
|
const url = resolve_valid_path(spec.path, spec.params);
|
|
174
|
-
// eslint-disable-next-line no-await-in-loop
|
|
175
174
|
const res = await test_app.app.request(url, {
|
|
176
175
|
method: spec.method,
|
|
177
176
|
headers: { host: 'localhost', origin: 'http://localhost:5173' },
|
|
178
177
|
});
|
|
179
178
|
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
180
|
-
await res.body?.cancel();
|
|
179
|
+
await res.body?.cancel();
|
|
181
180
|
continue;
|
|
182
181
|
}
|
|
183
182
|
let error_body;
|
|
184
183
|
try {
|
|
185
|
-
error_body = await res.clone().json();
|
|
184
|
+
error_body = await res.clone().json();
|
|
186
185
|
}
|
|
187
186
|
catch {
|
|
188
187
|
continue;
|
|
@@ -200,7 +199,6 @@ const describe_data_exposure_runtime_tests = (options) => {
|
|
|
200
199
|
continue;
|
|
201
200
|
const url = resolve_valid_path(spec.path, spec.params);
|
|
202
201
|
const headers = authed_account.create_session_headers();
|
|
203
|
-
// eslint-disable-next-line no-await-in-loop
|
|
204
202
|
const res = await test_app.app.request(url, {
|
|
205
203
|
method: spec.method,
|
|
206
204
|
headers,
|
|
@@ -208,7 +206,7 @@ const describe_data_exposure_runtime_tests = (options) => {
|
|
|
208
206
|
assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
|
|
209
207
|
let error_body;
|
|
210
208
|
try {
|
|
211
|
-
error_body = await res.clone().json();
|
|
209
|
+
error_body = await res.clone().json();
|
|
212
210
|
}
|
|
213
211
|
catch {
|
|
214
212
|
continue;
|
|
@@ -247,16 +245,16 @@ const describe_data_exposure_runtime_tests = (options) => {
|
|
|
247
245
|
},
|
|
248
246
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
249
247
|
};
|
|
250
|
-
const res = await test_app.app.request(url, request_init);
|
|
248
|
+
const res = await test_app.app.request(url, request_init);
|
|
251
249
|
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
252
|
-
await res.body?.cancel();
|
|
250
|
+
await res.body?.cancel();
|
|
253
251
|
continue;
|
|
254
252
|
}
|
|
255
253
|
if (!res.ok)
|
|
256
254
|
continue;
|
|
257
255
|
let response_body;
|
|
258
256
|
try {
|
|
259
|
-
response_body = await res.clone().json();
|
|
257
|
+
response_body = await res.clone().json();
|
|
260
258
|
}
|
|
261
259
|
catch {
|
|
262
260
|
continue;
|
package/dist/testing/db.js
CHANGED
|
@@ -206,7 +206,7 @@ export const AUTH_DROP_TABLES = [
|
|
|
206
206
|
*/
|
|
207
207
|
export const drop_auth_schema = async (db) => {
|
|
208
208
|
for (const table of AUTH_DROP_TABLES) {
|
|
209
|
-
await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`);
|
|
209
|
+
await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`);
|
|
210
210
|
}
|
|
211
211
|
await db.query('DROP TABLE IF EXISTS schema_version CASCADE');
|
|
212
212
|
};
|
|
@@ -840,7 +840,6 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
840
840
|
const route = find_auth_route(test_app.route_specs, suffix, suffix === '/tokens/create' || suffix === '/sessions/revoke-all' ? 'POST' : 'GET');
|
|
841
841
|
if (!route)
|
|
842
842
|
continue;
|
|
843
|
-
// eslint-disable-next-line no-await-in-loop
|
|
844
843
|
const res = await test_app.app.request(route.path, {
|
|
845
844
|
method: route.method,
|
|
846
845
|
headers: { host: 'localhost' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,
|
|
1
|
+
{"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IA4N/E,CAAC"}
|
|
@@ -64,7 +64,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
64
64
|
const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
|
|
65
65
|
assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
|
|
66
66
|
// Fire max_attempts failed login requests (sequential — must exhaust the window)
|
|
67
|
-
/* eslint-disable no-await-in-loop */
|
|
68
67
|
for (let i = 0; i < max_attempts; i++) {
|
|
69
68
|
const res = await test_app.app.request(login_route.path, {
|
|
70
69
|
method: 'POST',
|
|
@@ -77,7 +76,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
77
76
|
});
|
|
78
77
|
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
79
78
|
}
|
|
80
|
-
/* eslint-enable no-await-in-loop */
|
|
81
79
|
// The next request should be rate limited
|
|
82
80
|
const blocked_res = await test_app.app.request(login_route.path, {
|
|
83
81
|
method: 'POST',
|
|
@@ -119,7 +117,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
119
117
|
assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
|
|
120
118
|
const target_username = 'rate_limit_target';
|
|
121
119
|
// Fire max_attempts failed login requests for the same username
|
|
122
|
-
/* eslint-disable no-await-in-loop */
|
|
123
120
|
for (let i = 0; i < max_attempts; i++) {
|
|
124
121
|
const res = await test_app.app.request(login_route.path, {
|
|
125
122
|
method: 'POST',
|
|
@@ -132,7 +129,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
132
129
|
});
|
|
133
130
|
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
134
131
|
}
|
|
135
|
-
/* eslint-enable no-await-in-loop */
|
|
136
132
|
// The next request for the same username should be rate limited
|
|
137
133
|
const blocked_res = await test_app.app.request(login_route.path, {
|
|
138
134
|
method: 'POST',
|
|
@@ -184,7 +180,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
184
180
|
const verify_route = find_auth_route(test_app.route_specs, '/verify', 'GET');
|
|
185
181
|
assert.ok(verify_route, 'Expected GET /verify route — ensure create_route_specs includes account routes');
|
|
186
182
|
// Fire max_attempts invalid bearer requests (sequential — must exhaust the window)
|
|
187
|
-
/* eslint-disable no-await-in-loop */
|
|
188
183
|
for (let i = 0; i < max_attempts; i++) {
|
|
189
184
|
const res = await test_app.app.request(verify_route.path, {
|
|
190
185
|
headers: {
|
|
@@ -194,7 +189,6 @@ export const describe_rate_limiting_tests = (options) => {
|
|
|
194
189
|
});
|
|
195
190
|
assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
|
|
196
191
|
}
|
|
197
|
-
/* eslint-enable no-await-in-loop */
|
|
198
192
|
// The next request should be rate limited
|
|
199
193
|
const blocked_res = await test_app.app.request(verify_route.path, {
|
|
200
194
|
headers: {
|
|
@@ -108,8 +108,8 @@ export const describe_rpc_round_trip_tests = (options) => {
|
|
|
108
108
|
const init = create_rpc_post_init(action.spec.method, params);
|
|
109
109
|
// merge auth headers into init
|
|
110
110
|
Object.assign(init.headers, headers);
|
|
111
|
-
const res = await test_app.app.request(ep_spec.path, init);
|
|
112
|
-
const body = await res.json();
|
|
111
|
+
const res = await test_app.app.request(ep_spec.path, init);
|
|
112
|
+
const body = await res.json();
|
|
113
113
|
// validate well-formed JSON-RPC; successful responses also checked against output schema
|
|
114
114
|
try {
|
|
115
115
|
if (res.ok) {
|
|
@@ -141,8 +141,8 @@ export const describe_rpc_round_trip_tests = (options) => {
|
|
|
141
141
|
const params = override ?? generate_valid_body(action.spec.input) ?? undefined;
|
|
142
142
|
const headers = pick_rpc_auth_headers(surface_method, test_app, authed_account, admin_account);
|
|
143
143
|
const url = create_rpc_get_url(ep_spec.path, action.spec.method, params);
|
|
144
|
-
const res = await test_app.app.request(url, { headers });
|
|
145
|
-
const body = await res.json();
|
|
144
|
+
const res = await test_app.app.request(url, { headers });
|
|
145
|
+
const body = await res.json();
|
|
146
146
|
try {
|
|
147
147
|
if (res.ok) {
|
|
148
148
|
assert_jsonrpc_success_response(body, action.spec.output);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/sse_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAmB7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAwB,KAAK,SAAS,EAAuB,MAAM,oBAAoB,CAAC;AAE/F,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAkB,KAAK,OAAO,EAAE,KAAK,WAAW,EAAC,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAM9D,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,WAAW,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,qDAAqD;IACrD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,8BAA8B;IAC9B,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"sse_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/sse_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAmB7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAwB,KAAK,SAAS,EAAuB,MAAM,oBAAoB,CAAC;AAE/F,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAkB,KAAK,OAAO,EAAE,KAAK,WAAW,EAAC,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAM9D,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAChC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,WAAW,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,qDAAqD;IACrD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,8BAA8B;IAC9B,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;CAChC;AAyHD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,mBAAmB,KAAG,IA4GvE,CAAC"}
|
|
@@ -57,7 +57,7 @@ const create_sse_frame_reader = (reader) => {
|
|
|
57
57
|
buffer = buffer.slice(idx + 2);
|
|
58
58
|
return frame;
|
|
59
59
|
}
|
|
60
|
-
const cont = await pump_once(timeout_ms);
|
|
60
|
+
const cont = await pump_once(timeout_ms);
|
|
61
61
|
if (!cont)
|
|
62
62
|
throw new Error('SSE stream ended before a frame was received');
|
|
63
63
|
}
|
|
@@ -72,7 +72,6 @@ const create_sse_frame_reader = (reader) => {
|
|
|
72
72
|
if (remaining <= 0)
|
|
73
73
|
return false;
|
|
74
74
|
try {
|
|
75
|
-
// eslint-disable-next-line no-await-in-loop
|
|
76
75
|
await pump_once(Math.min(remaining, timeout_ms));
|
|
77
76
|
}
|
|
78
77
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "fullstack app library",
|
|
5
5
|
"glyph": "🗝",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@fuzdev/gro": "^0.197.3",
|
|
51
51
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
52
52
|
"@node-rs/argon2": "^2.0.2",
|
|
53
|
-
"@ryanatkn/eslint-config": "^0.
|
|
53
|
+
"@ryanatkn/eslint-config": "^0.11.0",
|
|
54
54
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
55
55
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
56
56
|
"@sveltejs/kit": "^2.55.0",
|