@starci/hfs 4.7.41 → 4.7.42
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.7.42 - 2026-10-26
|
|
4
|
+
- Fixed: `HFS_BYPASS_MARKER` reads a skip/only index only (`describe["skip"]`), not a marker word inside an array literal.
|
|
5
|
+
- Fixed: the BE SQL owner analysis bounds a FROM-less and an aggregate-only SELECT as one row.
|
|
6
|
+
- Changed: the bundled canon pins follow the lint family bumps (canon-be 3.4.49, canon-fe 9.1.4, Grammar 0.12.8).
|
|
7
|
+
|
|
3
8
|
## 4.7.41 - 2026-10-26
|
|
4
9
|
- Changed: the bundled canon pins follow the lint family bumps (canon-be 3.4.48, canon-fe 9.1.3, Grammar 0.12.7). No behavior change.
|
|
5
10
|
|
package/package.json
CHANGED
|
@@ -23,35 +23,35 @@ pins:
|
|
|
23
23
|
source: packages/heroicons/package.json
|
|
24
24
|
why: 'StarCi custom Heroicons-compatible cuts; eslint-grammar-starci (icon.mjs) admits @starci/heroicons/24/outline and /16/solid as icon sources, so an app that uses them gets this exact version.'
|
|
25
25
|
'@starci/grammar':
|
|
26
|
-
version: 0.12.
|
|
26
|
+
version: 0.12.8
|
|
27
27
|
group: starci
|
|
28
28
|
install: registry
|
|
29
29
|
side: fe
|
|
30
30
|
source: packages/grammar/package.json
|
|
31
31
|
why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
|
|
32
32
|
'@starci/eslint-canon-be':
|
|
33
|
-
version: 3.4.
|
|
33
|
+
version: 3.4.49
|
|
34
34
|
group: starci
|
|
35
35
|
install: registry
|
|
36
36
|
side: be
|
|
37
37
|
source: packages/eslint/be/package.json
|
|
38
38
|
why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
|
|
39
39
|
'@starci/eslint-canon-fe':
|
|
40
|
-
version: 9.1.
|
|
40
|
+
version: 9.1.4
|
|
41
41
|
group: starci
|
|
42
42
|
install: registry
|
|
43
43
|
side: fe
|
|
44
44
|
source: packages/eslint/fe/package.json
|
|
45
45
|
why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
|
|
46
46
|
'@starci/eslint-grammar-starci':
|
|
47
|
-
version: 0.12.
|
|
47
|
+
version: 0.12.8
|
|
48
48
|
group: starci
|
|
49
49
|
install: registry
|
|
50
50
|
side: fe
|
|
51
51
|
source: packages/eslint/grammar-starci/package.json
|
|
52
52
|
why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
|
|
53
53
|
'@starci/eslint-grammar-offset-pop':
|
|
54
|
-
version: 0.12.
|
|
54
|
+
version: 0.12.8
|
|
55
55
|
group: starci
|
|
56
56
|
install: registry
|
|
57
57
|
side: fe
|
|
@@ -91,14 +91,14 @@ pins:
|
|
|
91
91
|
source: packages/test-world/package.json
|
|
92
92
|
why: 'the shared e2e library of every back end (R47, R48), a devDependency of every back end: the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock. Kafka is real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and Postgres is schema-per-context (a schema and a login role per context in a shared database). Each worker has its own data slot (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock; a run provisions min(jest workers, workers cap, default 2) slots, and state file protocol 2 pairs exactly with @starci/jest-preset (TEST_WORLD_PAIR_MISMATCH otherwise). A modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client, world.keycloak.events/sessions read the realm user events and live sessions, world.infra.postgresql.connection(name) takes one database connection down while the others serve, and world.resolve and scope.resolve take any Nest token. Every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json); the globalSetup registers the path aliases as TypeScript resolves them. Its CLI main is exported at ./cli for starci app stack.'
|
|
93
93
|
'@starci/cli':
|
|
94
|
-
version: 1.7.
|
|
94
|
+
version: 1.7.42
|
|
95
95
|
group: starci
|
|
96
96
|
install: registry
|
|
97
97
|
side: both
|
|
98
98
|
source: packages/cli/package.json
|
|
99
99
|
why: 'the only StarCi command package; product repositories install it for starci app, while @starci/hfs remains its pinned transitive implementation.'
|
|
100
100
|
'@starci/hfs':
|
|
101
|
-
version: 4.7.
|
|
101
|
+
version: 4.7.42
|
|
102
102
|
group: starci
|
|
103
103
|
install: registry
|
|
104
104
|
side: both
|
|
@@ -307,19 +307,48 @@ function singleFromRelation(tokens, level, from) {
|
|
|
307
307
|
return true;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
// Functions that return a set of rows when they stand in a select list. A select list holding one multiplies the rows.
|
|
311
|
+
const SET_RETURNING = new Set(`GENERATE_SERIES GENERATE_SUBSCRIPTS UNNEST REGEXP_MATCHES REGEXP_SPLIT_TO_TABLE STRING_TO_TABLE
|
|
312
|
+
JSON_EACH JSONB_EACH JSON_EACH_TEXT JSONB_EACH_TEXT JSON_ARRAY_ELEMENTS JSONB_ARRAY_ELEMENTS JSON_ARRAY_ELEMENTS_TEXT JSONB_ARRAY_ELEMENTS_TEXT
|
|
313
|
+
JSON_OBJECT_KEYS JSONB_OBJECT_KEYS JSON_TO_RECORDSET JSONB_TO_RECORDSET JSON_POPULATE_RECORDSET JSONB_POPULATE_RECORDSET
|
|
314
|
+
JSONB_PATH_QUERY JSONB_PATH_QUERY_TZ JSON_TABLE PG_LS_DIR PG_LS_WALDIR PG_LS_LOGDIR PG_LISTENING_CHANNELS PG_SHOW_ALL_SETTINGS PG_GET_KEYWORDS ACLEXPLODE TS_STAT TS_DEBUG`.split(/\s+/u));
|
|
315
|
+
// Scalar builtins and operator-like words that are no row-multiplying call; the aggregate-only shape accepts these around its aggregates.
|
|
316
|
+
const SCALAR_CALLS = new Set(`COALESCE NULLIF GREATEST LEAST ROUND CEIL CEILING FLOOR ABS TRUNC EXTRACT DATE_PART DATE_TRUNC TO_CHAR LOWER UPPER LENGTH CONCAT
|
|
317
|
+
JSONB_BUILD_OBJECT JSONB_BUILD_ARRAY JSON_BUILD_OBJECT JSON_BUILD_ARRAY TO_JSONB TO_JSON`.split(/\s+/u));
|
|
318
|
+
const NOT_A_CALL = new Set(['IN', 'ANY', 'ALL', 'SOME', 'AND', 'OR', 'NOT', 'FILTER', 'EXISTS', 'CASE', 'WHEN', 'THEN', 'ELSE', 'AS', 'ON', 'DISTINCT', 'BETWEEN', 'LIKE', 'ILIKE', 'IS', 'WITHIN', 'FROM']);
|
|
319
|
+
|
|
320
|
+
const callAt = (tokens, index) => isName(tokens[index]) && tokens[index + 1]?.v === '(' && !(tokens[index].t === 'word' && (NOT_A_CALL.has(tokens[index].up) || CLAUSE_WORDS.has(tokens[index].up)));
|
|
321
|
+
|
|
322
|
+
/** A call to a set-returning function anywhere in the statement (conservative: nested positions count). */
|
|
323
|
+
const hasSetReturningCall = tokens => tokens.some((token, index) => callAt(tokens, index) && token.t === 'word' && SET_RETURNING.has(token.up));
|
|
324
|
+
|
|
325
|
+
const hasWindow = tokens => tokens.some(token => isWord(token, 'OVER'));
|
|
326
|
+
|
|
327
|
+
/** Plain known aggregate calls and known scalar builtins are scalar; other calls, including wrappers around aggregates, may return sets. */
|
|
311
328
|
function unprovenProjectionCall(tokens, level, select, from) {
|
|
312
329
|
const end = from < 0 ? tokens.length : from;
|
|
313
330
|
for (let index = select + 1; index < end; index += 1) {
|
|
314
331
|
const token = tokens[index];
|
|
315
332
|
if (level[index] === 0 && (isWord(token, 'WHERE') || (token.t === 'word' && WHERE_END.has(token.up)))) break;
|
|
316
|
-
if (!
|
|
333
|
+
if (!callAt(tokens, index)) continue;
|
|
317
334
|
if (scalarCastAt(tokens, index)) continue;
|
|
318
|
-
|
|
335
|
+
const known = token.t === 'word' && tokens[index - 1]?.v !== '.' && (AGGREGATES.has(token.up) || SCALAR_CALLS.has(token.up));
|
|
336
|
+
if (!known) return true;
|
|
319
337
|
}
|
|
320
338
|
return false;
|
|
321
339
|
}
|
|
322
340
|
|
|
341
|
+
/**
|
|
342
|
+
* A SELECT that returns at most one row by construction: it has no FROM (one row, unless a set-returning function multiplies it),
|
|
343
|
+
* or it selects only aggregates (plain or inside known scalar calls and casts) with no GROUP BY, window or set-returning function.
|
|
344
|
+
*/
|
|
345
|
+
function singleRowBound(tokens, level, select, from, grouped) {
|
|
346
|
+
if (hasSetReturningCall(tokens) || hasWindow(tokens)) return null;
|
|
347
|
+
if (from < 0) return grouped ? null : { bounded: true, basis: 'no from' };
|
|
348
|
+
if (!grouped && !unprovenProjectionCall(tokens, level, select, from) && aggregateSelectItems(tokens, level, select, from).every(isAggregateItem)) return { bounded: true, basis: 'aggregate' };
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
323
352
|
function selectBound(tokens, level, uniqueSetsOf, cte) {
|
|
324
353
|
if (findTopWord(tokens, level, 'LIMIT') >= 0 || findTopWord(tokens, level, 'FETCH') >= 0) return { bounded: true, basis: 'limit' };
|
|
325
354
|
const select = findTopWord(tokens, level, 'SELECT');
|
|
@@ -328,10 +357,10 @@ function selectBound(tokens, level, uniqueSetsOf, cte) {
|
|
|
328
357
|
const unbounded = { bounded: false, basis: 'unbounded', table: primary?.table ?? null };
|
|
329
358
|
const grouped = hasTopGroupBy(tokens, level);
|
|
330
359
|
if (tokens.some((token, index) => level[index] === 0 && (isWord(token, 'UNION') || isWord(token, 'INTERSECT') || isWord(token, 'EXCEPT')))) return unbounded;
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
if (
|
|
334
|
-
if (grouped || !primary || primary.dynamic || cte.has(primary.table) || !singleFromRelation(tokens, level, from)) return unbounded;
|
|
360
|
+
const single = singleRowBound(tokens, level, select, from, grouped);
|
|
361
|
+
if (single) return single;
|
|
362
|
+
if (unprovenProjectionCall(tokens, level, select, from) || hasSetReturningCall(tokens)) return unbounded;
|
|
363
|
+
if (grouped || from < 0 || !primary || primary.dynamic || cte.has(primary.table) || !singleFromRelation(tokens, level, from)) return unbounded;
|
|
335
364
|
if (isUniqueBounded(tokens, level, from, primary, uniqueSetsOf(primary.table, primary.schema))) return { bounded: true, basis: 'unique' };
|
|
336
365
|
return unbounded;
|
|
337
366
|
}
|
|
@@ -230,8 +230,20 @@ function skipsHooks(piece, code, strings) {
|
|
|
230
230
|
return around !== null && strings.some((other) => other.at > around[0] && other.at < around[1] && GIT_VERBS.has(other.text));
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
/**
|
|
234
|
-
const
|
|
233
|
+
/** Words after which a `[` opens an array literal, not a member access. */
|
|
234
|
+
const BEFORE_ARRAY_LITERAL = /(?:^|[^\w$.])(?:return|typeof|in|of|case|yield|await|void|new|delete|else|do|throw|default|extends)$/;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A test marker spelled as an index (`describe['skip']`): a string equal to a marker that is the whole key of a member access,
|
|
238
|
+
* `[` before it after an expression and `]` right after it. An array element (`["skip", "idea"]`, `const kinds = ['only']`) is data.
|
|
239
|
+
*/
|
|
240
|
+
function indexedMarker(piece, code) {
|
|
241
|
+
if (!TEST_KEYS.has(piece.text)) return false;
|
|
242
|
+
const before = code.slice(0, piece.at).trimEnd();
|
|
243
|
+
if (!before.endsWith('[') || !code.slice(piece.at + piece.text.length).trimStart().startsWith(']')) return false;
|
|
244
|
+
const subject = before.slice(0, -1).trimEnd();
|
|
245
|
+
return /[\w$)\]]$/.test(subject) && !BEFORE_ARRAY_LITERAL.test(subject);
|
|
246
|
+
}
|
|
235
247
|
|
|
236
248
|
/** An `exit` handler of the process that resets the exit code or swallows the failure. */
|
|
237
249
|
function exitHandler(piece, code) {
|