@fuzdev/fuz_app 0.24.0 → 0.25.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.
Files changed (54) hide show
  1. package/dist/actions/action_codegen.d.ts +25 -0
  2. package/dist/actions/action_codegen.d.ts.map +1 -1
  3. package/dist/actions/action_codegen.js +39 -0
  4. package/dist/actions/action_peer.d.ts +7 -0
  5. package/dist/actions/action_peer.d.ts.map +1 -1
  6. package/dist/actions/action_peer.js +1 -1
  7. package/dist/actions/action_types.d.ts +16 -1
  8. package/dist/actions/action_types.d.ts.map +1 -1
  9. package/dist/actions/cancel.d.ts +78 -0
  10. package/dist/actions/cancel.d.ts.map +1 -0
  11. package/dist/actions/cancel.js +79 -0
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +50 -17
  14. package/dist/actions/rpc_client.d.ts +10 -0
  15. package/dist/actions/rpc_client.d.ts.map +1 -1
  16. package/dist/actions/rpc_client.js +22 -7
  17. package/dist/actions/socket.svelte.d.ts +22 -10
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +46 -12
  20. package/dist/actions/transports.d.ts +14 -3
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports_http.d.ts +3 -3
  23. package/dist/actions/transports_http.d.ts.map +1 -1
  24. package/dist/actions/transports_http.js +4 -3
  25. package/dist/actions/transports_ws.d.ts +33 -6
  26. package/dist/actions/transports_ws.d.ts.map +1 -1
  27. package/dist/actions/transports_ws.js +43 -46
  28. package/dist/actions/transports_ws_backend.d.ts +12 -3
  29. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  30. package/dist/actions/transports_ws_backend.js +12 -1
  31. package/dist/auth/bearer_auth.js +0 -1
  32. package/dist/auth/keyring.d.ts.map +1 -1
  33. package/dist/auth/keyring.js +0 -2
  34. package/dist/auth/migrations.js +4 -4
  35. package/dist/db/migrate.d.ts +12 -2
  36. package/dist/db/migrate.d.ts.map +1 -1
  37. package/dist/db/migrate.js +25 -16
  38. package/dist/db/status.d.ts.map +1 -1
  39. package/dist/db/status.js +0 -2
  40. package/dist/dev/setup.js +2 -2
  41. package/dist/http/db_routes.d.ts.map +1 -1
  42. package/dist/http/db_routes.js +0 -1
  43. package/dist/testing/admin_integration.d.ts.map +1 -1
  44. package/dist/testing/admin_integration.js +0 -3
  45. package/dist/testing/app_server.js +1 -1
  46. package/dist/testing/data_exposure.js +6 -8
  47. package/dist/testing/db.js +1 -1
  48. package/dist/testing/integration.js +0 -1
  49. package/dist/testing/rate_limiting.d.ts.map +1 -1
  50. package/dist/testing/rate_limiting.js +0 -6
  51. package/dist/testing/rpc_round_trip.js +4 -4
  52. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  53. package/dist/testing/sse_round_trip.js +1 -2
  54. package/package.json +2 -2
@@ -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
- * Each migration runs in its own transaction.
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 (each in its own transaction),
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, each in its own transaction with version upsert
82
- for (let i = current_version; i < migrations.length; i++) {
83
- const { fn, name } = resolve_migration(migrations[i]);
84
- const label = name != null ? `"${name}"` : '';
85
- try {
86
- await db.transaction(async (tx) => {
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
- catch (err) {
95
- const name_part = label ? ` ${label}` : '';
96
- throw new Error(`Migration ${namespace}[${i}]${name_part} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
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
  };
@@ -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,CA+ElB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MA6BnD,CAAC"}
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.js CHANGED
@@ -93,7 +93,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
93
93
  for (const [key, generate] of Object.entries(replacements)) {
94
94
  const pattern = new RegExp(`^${key}=$`, 'm');
95
95
  if (pattern.test(content)) {
96
- const value = await generate(); // eslint-disable-line no-await-in-loop
96
+ const value = await generate();
97
97
  content = content.replace(pattern, `${key}=${value}`);
98
98
  changed = true;
99
99
  log.ok(`Generated ${key} in existing ${env_path}`);
@@ -114,7 +114,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
114
114
  for (const [key, generate] of Object.entries(replacements)) {
115
115
  const pattern = new RegExp(`^${key}=$`, 'm');
116
116
  if (pattern.test(content)) {
117
- const value = await generate(); // eslint-disable-line no-await-in-loop
117
+ const value = await generate();
118
118
  content = content.replace(pattern, `${key}=${value}`);
119
119
  }
120
120
  }
@@ -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,CAuN9E,CAAC"}
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"}
@@ -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,IAwnCF,CAAC"}
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 }); // eslint-disable-line no-await-in-loop
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(); // eslint-disable-line no-await-in-loop
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(); // eslint-disable-line no-await-in-loop
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(); // eslint-disable-line no-await-in-loop
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); // eslint-disable-line no-await-in-loop
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(); // eslint-disable-line no-await-in-loop
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(); // eslint-disable-line no-await-in-loop
257
+ response_body = await res.clone().json();
260
258
  }
261
259
  catch {
262
260
  continue;
@@ -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`); // eslint-disable-line no-await-in-loop
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,IA+N/E,CAAC"}
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); // eslint-disable-line no-await-in-loop
112
- const body = await res.json(); // eslint-disable-line no-await-in-loop
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 }); // eslint-disable-line no-await-in-loop
145
- const body = await res.json(); // eslint-disable-line no-await-in-loop
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;AA0HD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,mBAAmB,KAAG,IA4GvE,CAAC"}
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); // eslint-disable-line no-await-in-loop
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.24.0",
3
+ "version": "0.25.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.10.1",
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",