@stackone/datasync 1.0.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/README.md +19 -0
- package/dist/chunk-BhWHdk3m.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +115 -0
- package/dist/index.d.mts +116 -0
- package/dist/index.mjs +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @stackone/datasync
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
The DataSync document model and s1query grammar, extracted from `unified-cloud-api` so its read and write
|
|
6
|
+
paths and the `datasync-read-handler` Lambda can share one source of truth:
|
|
7
|
+
|
|
8
|
+
- `documents` — flatten/unflatten synced items, infer field types, allocate field-map slots.
|
|
9
|
+
- `query` / `aggregations` — validate and translate the caller-supplied s1query `filter`, `search`,
|
|
10
|
+
`source`, `sort` and `aggs` into OpenSearch DSL over aliased columns.
|
|
11
|
+
- `limits` — page bounds shared by every surface.
|
|
12
|
+
- `opensearch` — tenant-scoped search/aggregation body builders and response predicates.
|
|
13
|
+
- `describeOpenSearchRejection` returns `{ message, parameters, reason }` only; the consumer adds its own transport fields (`error`, `statusCode`), unlike `SyncedQueryRejection.toResponse()` which is complete.
|
|
14
|
+
- `buildAggregationBody` takes `TranslatedAggregations` from `translateAggs`, not a raw object, so an untranslated aggregation cannot reach the request.
|
|
15
|
+
- Both body builders accept only translator outputs — `TranslatedQuery`, `TranslatedSource`, `TranslatedSort`, `FullTextQuery` and `TranslatedAggregations` — never raw caller objects.
|
|
16
|
+
- `errors` — `SyncedQueryRejection`, the framework-free 400 every consumer maps to its own transport.
|
|
17
|
+
|
|
18
|
+
This package uses the tech stack provided by the **Connect Monorepo** global setup. Please check the
|
|
19
|
+
[root README](../../README.md) for more information.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name as t};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0}),SyncedQueryRejection=class extends Error{constructor(e,t){super(e),this.name=`SyncedQueryRejection`,this.details=t}toResponse(){return{...this.details,message:this.message,error:`Bad Request`,statusCode:400}}};const syncedQueryError=(e,t)=>new SyncedQueryRejection(e,t),t=[`term`,`terms`,`range`,`wildcard`,`match`,`match_phrase`,`exists`,`bool`],n=`OpenSearch-style filter (Query DSL) (${t.join(` / `)}) over synced fields. A filter is one clause: an object with exactly one query type, keyed by field name, e.g. {"term": {"<field>": <value>}}, {"terms": {"<field>": [...]}}, {"range": {"<field>": {"gte": ...}}}; combine clauses with {"bool": {"filter": [...], "must_not": [...]}}. Bare {"<field>": <value>} maps are rejected. match / match_phrase do word-level search (string fields only, case-insensitive); wildcard matches patterns (case-sensitive).`;function buildFullTextQuery(e){let t=e.trim();if(t)return{simple_query_string:{query:t,fields:[`data.*.text`]}}}const r=__name(e=>typeof e==`object`&&!!e&&!Array.isArray(e),`isPlainObject`),isScalar=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,isStringOrNumber=e=>typeof e==`string`||typeof e==`number`,isNumeric=e=>typeof e==`number`||typeof e==`string`&&e.trim()!==``&&Number.isFinite(Number(e)),describeValue=e=>e===null?`null`:Array.isArray(e)?`an array`:`a ${typeof e}`,describeAvailableFields=e=>{let t=[...e.keys()];if(t.length===0)return`No fields are queryable for this sync yet`;let n=t.slice(0,50),r=t.length-n.length,i=r>0?`, … and ${r} more`:``;return`Available fields: ${n.join(`, `)}${i}`},resolveField=(e,t,n=`filter`)=>{let r=t.get(e);if(!r)throw syncedQueryError(`Field '${e}' is not available for querying. ${describeAvailableFields(t)}`,{parameter:n,field:e,available_fields:[...t.keys()]});return`data.${r}`},filterError=(e,t)=>syncedQueryError(e,{parameter:`filter`,path:t}),i=new Set([`term`,`terms`,`range`,`wildcard`]),a=new Set([`match`,`match_phrase`]),o=new Set([`must`,`should`,`filter`,`must_not`]),s=new Set([`minimum_should_match`]),c=[`value`,`boost`,`case_insensitive`],l=[`value`,`boost`,`case_insensitive`,`rewrite`],u=[`query`,`operator`,`minimum_should_match`,`fuzziness`,`prefix_length`,`max_expansions`,`zero_terms_query`,`boost`],d=[`query`,`slop`,`zero_terms_query`,`boost`],f=new Set([`gt`,`gte`,`lt`,`lte`]),p=new Set([`format`,`time_zone`]),unsupportedQueryType=(e,n,r)=>{let i=`Supported types: ${t.join(`, `)}`;return n.aliasMap.has(e)?filterError(`'${e}' is a queryable field, not a query type. Wrap it in a clause, e.g. {"term": {"${e}": ...}}. ${i}`,r):filterError(`Unsupported query type '${e}'. ${i}`,r)},assertSupportedOptions=(e,t,n,r)=>{let i=Object.keys(t).find(e=>!n.includes(e));if(i!==void 0)throw filterError(`'${e}' option '${i}' is not supported. Supported: ${n.join(`, `)}`,r)},validateTermValue=(e,t,n)=>{if(!isScalar(t)){if(r(t)&&isScalar(t.value)){assertSupportedOptions(`term`,t,c,n);return}throw filterError(`'term' on '${e}' must be a single string, number or boolean (or {"value": ...}); use 'terms' for a list of values`,n)}},validateTermsValue=(e,t,n)=>{if(!(Array.isArray(t)&&t.every(isScalar)))throw filterError(`'terms' on '${e}' must be an array of values`,n)},validateWildcardValue=(e,t,n,i)=>{if(n!==void 0&&n!==`string`)throw filterError(`'wildcard' is only supported on string fields; '${e}' is not a string field`,i);if(typeof t!=`string`){if(r(t)&&typeof t.value==`string`){assertSupportedOptions(`wildcard`,t,l,i);return}throw filterError(`'wildcard' on '${e}' must be a pattern string (or {"value": "<pattern>"})`,i)}},validateTextValue=(e,t,n,i)=>{if(typeof n!=`string`){if(r(n)&&isScalar(n.query)){assertSupportedOptions(e,n,e===`match`?u:d,i);return}throw filterError(`'${e}' on '${t}' must be a string or a { "query": ... } object`,i)}},validateRangeBound=(e,t,n,r,i)=>{if(!isStringOrNumber(n))throw filterError(`'range' bound '${t}' on '${e}' must be a string or number`,i);if((r===`int`||r===`float`)&&!isNumeric(n))throw filterError(`'range' bound '${t}' on numeric field '${e}' must be a number`,i);if(r===`date`&&typeof n==`boolean`)throw filterError(`'range' bound '${t}' on date field '${e}' must be an ISO date string, date-math expression or epoch millis`,i)},validateRangeValue=(e,t,n,i)=>{if(n===`bool`)throw filterError(`'range' is not supported on boolean field '${e}'`,i);if(!r(t))throw filterError(`'range' on '${e}' must be an object of bounds, e.g. {"gte": ..., "lte": ...}`,i);let a=Object.keys(t);for(let r of a){if(f.has(r)){validateRangeBound(e,r,t[r],n,i);continue}if(p.has(r)){if(n!==void 0&&n!==`date`)throw filterError(`'range' option '${r}' is only supported on date fields; '${e}' is a ${n} field`,i);continue}throw filterError(`'range' operator '${r}' is not supported. Supported: ${[...f].join(`, `)} (plus ${[...p].join(`, `)} on date fields)`,i)}if(!a.some(e=>f.has(e)))throw filterError(`'range' on '${e}' needs at least one bound (${[...f].join(`, `)})`,i)},translateFieldClause=(e,t,n,i)=>{if(!r(t))throw filterError(`'${e}' clause at '${i}' must be an object keyed by field name, e.g. {"${e}": {"<field>": ...}}`,i);let o=Object.keys(t);if(o.length!==1)throw filterError(`'${e}' clause at '${i}' must name exactly one field, got: ${o.join(`, `)||`none`}`,i);let[s]=o,c=t[s],l=`${i}.${s}`,u=resolveField(s,n.aliasMap),d=n.fieldTypes.get(s);if(a.has(e)){if(d!==`string`)throw filterError(`'${e}' is only supported on string fields; '${s}' is not a string field`,l);return validateTextValue(e,s,c,l),{[e]:{[`${u}.text`]:c}}}return e===`term`?validateTermValue(s,c,l):e===`terms`?validateTermsValue(s,c,l):e===`range`?validateRangeValue(s,c,d,l):validateWildcardValue(s,c,d,l),{[e]:{[u]:c}}},translateExists=(e,t,n)=>{if(!r(e)||typeof e.field!=`string`)throw filterError(`'exists' query requires a string 'field'`,n);let i=Object.keys(e).filter(e=>e!==`field`);if(i.length>0)throw filterError(`'exists' only accepts 'field'; unsupported: ${i.join(`, `)}`,n);return{field:resolveField(e.field,t.aliasMap)}},translateBool=(e,t,n,i)=>{if(!r(e))throw filterError(`'bool' clause at '${n}' must be an object of clauses (${[...o].join(`, `)})`,n);let a={};for(let[r,c]of Object.entries(e)){if(o.has(r)){a[r]=(Array.isArray(c)?c:[c]).map((e,a)=>translateNode(e,t,`${n}.${r}[${a}]`,i+1));continue}if(s.has(r)){if(typeof c!=`number`&&typeof c!=`string`)throw filterError(`'bool' option '${r}' must be a number or string`,n);a[r]=c;continue}throw filterError(`'bool' option '${r}' is not supported. Supported: ${[...o,...s].join(`, `)}`,n)}return a},translateNode=(e,t,n,o)=>{if(o>10)throw filterError(`Filter nesting too deep (maximum 10 levels)`,n);if(!r(e))throw filterError(`Filter clause at '${n}' must be an object, got ${describeValue(e)}`,n);let s=Object.keys(e);if(s.length===0)throw filterError(`Filter clause at '${n}' is empty`,n);if(s.length>1)throw filterError(`Filter clause at '${n}' must contain exactly one query type, got: ${s.join(`, `)}. Combine clauses with {"bool": {"filter": [...]}}`,n);let[c]=s,l=e[c],u=`${n}.${c}`;if(c===`bool`)return{bool:translateBool(l,t,u,o)};if(c===`exists`)return{exists:translateExists(l,t,u)};if(i.has(c)||a.has(c))return translateFieldClause(c,l,t,u);throw unsupportedQueryType(c,t,n)},translateQuery=(e,t,n=new Map)=>translateNode(e,{aliasMap:t,fieldTypes:n},`filter`,0);function translateSourceFields(e,t){return e.map(e=>resolveField(e,t,`source`))}const m=new Set([`asc`,`desc`]),h=new Set([`min`,`max`,`sum`,`avg`,`median`]),g=[`order`,`missing`,`mode`],sortError=e=>syncedQueryError(e,{parameter:`sort`}),validateSortOptions=(e,t)=>{let n=sortError(`'sort' order for '${e}' must be "asc" or "desc"`);if(typeof t==`string`){if(!m.has(t))throw n;return}if(!r(t))throw sortError(`'sort' options for '${e}' must be "asc", "desc" or an object like {"order": "desc"}`);let i=Object.keys(t).find(e=>!g.includes(e));if(i!==void 0)throw sortError(`'sort' option '${i}' is not supported. Supported: ${g.join(`, `)}`);if(`order`in t&&!(typeof t.order==`string`&&m.has(t.order)))throw n;if(`mode`in t&&!(typeof t.mode==`string`&&h.has(t.mode)))throw sortError(`'sort' mode for '${e}' must be one of ${[...h].join(`, `)}`)};function translateSortFields(e,t){return e.map(e=>{if(typeof e==`string`)return resolveField(e,t,`sort`);if(r(e)){let n=Object.keys(e);if(n.length!==1)throw sortError(`Each sort entry must reference exactly one field`);let[r]=n,i=resolveField(r,t,`sort`);return validateSortOptions(r,e[r]),{[i]:e[r]}}throw sortError(`Each sort entry must be a field name or a '{ field: order }' object`)})}const _=1e3,v=[`avg`,`sum`,`min`,`max`,`value_count`,`stats`,`extended_stats`,`cardinality`,`percentiles`],y=[`terms`,`date_histogram`,`histogram`,`range`,`date_range`],b=new Set([...v,...y]),x=new Set([`__proto__`,`constructor`,`prototype`]),S=[`int`,`float`],C={avg:{label:`a numeric`,types:S},sum:{label:`a numeric`,types:S},stats:{label:`a numeric`,types:S},extended_stats:{label:`a numeric`,types:S},percentiles:{label:`a numeric`,types:S},histogram:{label:`a numeric`,types:S},range:{label:`a numeric`,types:S},min:{label:`a numeric or date`,types:[...S,`date`]},max:{label:`a numeric or date`,types:[...S,`date`]},date_histogram:{label:`a date`,types:[`date`]},date_range:{label:`a date`,types:[`date`]}},w=[`missing`,`format`],T={avg:w,sum:w,min:w,max:w,value_count:w,stats:w,extended_stats:w,cardinality:[`precision_threshold`,`missing`,`format`],percentiles:[`percents`,`keyed`,`missing`,`format`],terms:[`size`,`missing`,`min_doc_count`,`order`],date_histogram:[`calendar_interval`,`fixed_interval`,`interval`,`offset`,`time_zone`,`format`,`min_doc_count`,`order`,`missing`,`keyed`],histogram:[`interval`,`offset`,`min_doc_count`,`order`,`missing`,`keyed`],range:[`ranges`,`keyed`,`missing`],date_range:[`ranges`,`format`,`time_zone`,`keyed`,`missing`]},E=new Set([`aggs`,`aggregations`]),D={date_histogram:{keys:[`calendar_interval`,`fixed_interval`,`interval`],exactlyOne:!0},histogram:{keys:[`interval`]},range:{keys:[`ranges`]},date_range:{keys:[`ranges`]}};function isPlainObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}const O=new Set([`from`,`to`,`key`]);function rangeEntryKeys(e){return Object.keys(e).every(e=>O.has(e))}const aggsError=e=>syncedQueryError(e,{parameter:`aggs`});function assertAggFieldType(e,t,n){let r=C[e],i=n.fieldTypes.get(t);if(!(!r||i===void 0||r.types.includes(i)))throw syncedQueryError(`'${e}' requires ${r.label} field; '${t}' is a ${i} field`,{parameter:`aggs`,field:t})}function translateAggBody(e,t,n){if(!isPlainObject(t))throw aggsError(`Aggregation definition must be an object`);let r=T[e]??[],i={};for(let[a,o]of Object.entries(t)){if(a===`field`){if(typeof o!=`string`)throw aggsError(`Aggregation 'field' must be a string`);i[a]=resolveField(o,n.aliasMap,`aggs`),assertAggFieldType(e,o,n);continue}if(!r.includes(a))throw aggsError(`Aggregation option '${a}' is not supported for '${e}'. Supported: field, ${r.join(`, `)}`);if(a===`size`){if(typeof o!=`number`||!Number.isInteger(o)||o<1)throw aggsError(`Aggregation 'size' must be a positive integer`);if(o>_)throw aggsError(`Aggregation 'size' exceeds the maximum of ${_}`)}if(a===`ranges`){if(!Array.isArray(o)||o.length===0||!o.every(e=>isPlainObject(e)&&rangeEntryKeys(e)))throw aggsError(`Aggregation 'ranges' must be an array of {from, to, key} objects`);if(o.length>100)throw aggsError(`Aggregation 'ranges' exceeds the maximum of 100 entries`)}if(a===`percents`){if(!Array.isArray(o)||o.length===0||!o.every(e=>typeof e==`number`&&Number.isFinite(e)&&e>=0&&e<=100))throw aggsError(`Aggregation 'percents' must be an array of numbers between 0 and 100`);if(o.length>100)throw aggsError(`Aggregation 'percents' exceeds the maximum of 100 entries`)}if(a===`interval`){if(e===`date_histogram`){if(typeof o!=`string`||o.length===0)throw aggsError(`Aggregation 'interval' must be a non-empty string`)}else if(typeof o!=`number`||!Number.isFinite(o)||o<=0)throw aggsError(`Aggregation 'interval' must be a finite number greater than 0`)}if((a===`fixed_interval`||a===`calendar_interval`)&&(typeof o!=`string`||o.length===0))throw aggsError(`Aggregation '${a}' must be a non-empty string`);if(a===`min_doc_count`&&(typeof o!=`number`||!Number.isInteger(o)||o<0))throw aggsError(`Aggregation 'min_doc_count' must be a non-negative integer`);if(a===`keyed`&&typeof o!=`boolean`)throw aggsError(`Aggregation 'keyed' must be a boolean`);i[a]=o}if(!(`field`in t))throw aggsError(`Aggregation '${e}' requires a 'field'`);let a=D[e];if(a){let n=a.keys.filter(e=>e in t);if(a.exactlyOne&&n.length!==1)throw aggsError(`Aggregation '${e}' requires exactly one of: ${a.keys.join(`, `)}`);if(!a.exactlyOne&&n.length===0)throw a.keys.length===1?aggsError(`Aggregation '${e}' requires '${a.keys[0]}'`):aggsError(`Aggregation '${e}' requires one of: ${a.keys.join(`, `)}`)}return i}function translateAggDefinition(e,t,n,r){if(!isPlainObject(t))throw aggsError(`Aggregation definition must be an object`);let i=Object.keys(t).filter(e=>e!==`meta`&&!E.has(e));if(i.length!==1)throw aggsError(`Aggregation '${e}' must define exactly one aggregation type, got${i.length===0?` none`:`: ${i.join(`, `)}`}`);let a={};for(let[e,i]of Object.entries(t)){if(E.has(e)){a[e]=translateAggMap(i,n,r+1);continue}if(e===`meta`){a[e]=i;continue}if(!b.has(e))throw aggsError(`Aggregation type '${e}' is not supported. Supported types: ${[...b].join(`, `)}`);a[e]=translateAggBody(e,i,n)}return a}function translateAggMap(e,t,n){if(n>5)throw aggsError(`Aggregation nesting too deep`);if(!isPlainObject(e))throw aggsError(`Aggregations must be an object`);let r={};for(let[i,a]of Object.entries(e)){if(x.has(i))throw aggsError(`Aggregation name '${i}' is reserved`);if(t.definitions+=1,t.definitions>50)throw aggsError(`Aggregations exceed the maximum of 50 definitions`);r[i]=translateAggDefinition(i,a,t,n)}return r}function translateAggs(e,t,n=new Map){return translateAggMap(e,{aliasMap:t,fieldTypes:n,definitions:0},0)}const k=.9,A=new Set([`__proto__`,`constructor`,`prototype`]),hasForbiddenSegment=e=>e.replace(/\[\]/g,``).split(`.`).some(e=>A.has(e));function processValue(e,t,n,r){if(t!=null&&!(n>=5)){if(Array.isArray(t)){let i=t.filter(e=>e!=null);if(i.length===0)return;let a=i[0];if(typeof a==`object`&&!Array.isArray(a))for(let t of i)processObject(`${e}[]`,t,n+2,r);else r.has(e)||r.set(e,[]),r.get(e)?.push(...i);return}if(typeof t==`object`){processObject(e,t,n+1,r);return}r.has(e)||r.set(e,[]),r.get(e)?.push(t)}}function processObject(e,t,n,r){if(!(n>=5))for(let[i,a]of Object.entries(t))A.has(i)||processValue(e?`${e}.${i}`:i,a,n,r)}function collectFieldValues(e){let t=new Map;for(let n of e)processObject(``,n,0,t);return t}function flattenItem(e){let t=new Map;return flattenObject(``,e,0,t),Object.fromEntries(t)}function flattenValue(e,t,n,r){if(t!=null&&!(n>=5)){if(Array.isArray(t)){if(t.length===0)return;let i=t.find(e=>e!=null);if(typeof i==`object`&&!Array.isArray(i))flattenArrayOfObjects(e,t,n,r);else{let n=t.filter(e=>e!=null);n.length>0&&r.set(e,n)}return}if(typeof t==`object`){flattenObject(e,t,n+1,r);return}r.set(e,t)}}function flattenObject(e,t,n,r){if(!(n>=5))for(let[i,a]of Object.entries(t))A.has(i)||flattenValue(e?`${e}.${i}`:i,a,n,r)}function flattenArrayOfObjects(e,t,n,r){let i=`${e}[]`,a=[],o=[],s=new Set;for(let e of t){if(typeof e!=`object`||!e||Array.isArray(e)){a.push(new Map);continue}let t=new Map;flattenObject(i,e,n+2,t),a.push(t);for(let e of t.keys())s.has(e)||(s.add(e),o.push(e))}for(let e of o){let n=t.map((t,n)=>{let r=a[n].get(e);return r===void 0?null:r});r.set(e,n)}}function unflattenItem(e){let t={},n=new Map;for(let[r,i]of Object.entries(e)){if(hasForbiddenSegment(r)||(r.match(/\[\]/g)??[]).length>5)continue;let e=r.indexOf(`[].`);if(e!==-1){let t=r.substring(0,e),a=r.substring(e+3);n.has(t)||n.set(t,new Map),n.get(t)?.set(a,i);continue}let a=r.split(`.`),o=t;for(let e=0;e<a.length-1;e++){let t=a[e];(!Object.prototype.hasOwnProperty.call(o,t)||typeof o[t]!=`object`||o[t]===null)&&(o[t]={}),o=o[t]}o[a[a.length-1]]=i}for(let[e,r]of n.entries()){let n=0;for(let e of r.values())Array.isArray(e)&&(n=Math.max(n,e.length));let i=[];for(let e=0;e<n;e++){let t={};for(let[n,i]of r.entries())t[n]=Array.isArray(i)?i[e]:i;i.push(unflattenItem(t))}let a=e.split(`.`),o=t;for(let e=0;e<a.length-1;e++){let t=a[e];(!Object.prototype.hasOwnProperty.call(o,t)||typeof o[t]!=`object`||o[t]===null)&&(o[t]={}),o=o[t]}o[a[a.length-1]]=i}return t}const j=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;function isValidCalendarDate(e){let[t,n,r]=e.slice(0,10).split(`-`).map(Number);return n<1||n>12||r<1?!1:r<=new Date(Date.UTC(t,n,0)).getUTCDate()&&!Number.isNaN(Date.parse(e))}function inferPrimitiveType(e){return typeof e==`boolean`?`bool`:typeof e==`number`?Number.isInteger(e)?`int`:`float`:typeof e==`string`?j.test(e)&&isValidCalendarDate(e)?`date`:`string`:null}function determineFieldType(e){let t=e.map(inferPrimitiveType).filter(e=>e!==null);if(t.length===0)return null;let n=t.reduce((e,t)=>(e[t]=(e[t]??0)+1,e),{});if(((n.int??0)+(n.float??0))/t.length>=k)return(n.float??0)>0?`float`:`int`;let r=Object.entries(n).sort((e,t)=>t[1]-e[1])[0];return r[1]/t.length>=k?r[0]:`string`}function findNextAvailableSlot(e,t){let n=1;for(;t.has(`${e}${n}`);)n++;return`${e}${n}`}const M=1e4;function assertPageWindow(e,t){if(e!==void 0&&(!Number.isInteger(e)||e<1))throw syncedQueryError(`page_size ${e} must be an integer of at least 1`,{parameter:`page_size`,min_page_size:1});if(t!==void 0&&(!Number.isInteger(t)||t<0))throw syncedQueryError(`skip ${t} must be an integer of 0 or greater`,{parameter:`skip`,min_skip:0});if(e!==void 0&&e>200)throw syncedQueryError(`page_size ${e} exceeds the maximum of 200`,{parameter:`page_size`,max_page_size:200});let n=(t??0)+(e??100);if(n>1e4)throw syncedQueryError(`skip + page_size (${n}${e===void 0?`, page_size defaulting to 100`:``}) exceeds the maximum result window of ${M}; narrow the filter instead of paging deeper`,{parameter:`skip`,max_result_window:M})}function buildTenantScopeFilter(e){let t=e.ref?[{term:{refs:e.ref}}]:[{term:{account_id:e.accountId}},{term:{params_hash:e.paramsHash}},{term:{last_full_run_id:e.lastFullRunId}}];return[{term:{project_id:e.projectId}},...t,{term:{action_id:e.actionId}}]}function buildSearchBody(e){let t=buildTenantScopeFilter(e.scope);e.additionalQuery&&t.push(e.additionalQuery);let n={filter:t};return e.textQuery&&(n.must=[e.textQuery]),{query:{bool:n},timeout:`10s`,size:e.size??100,from:e.from??0,...e.source?.length&&{_source:e.source},...e.sort?.length&&{sort:e.sort}}}function buildAggregationBody(e){let t=buildTenantScopeFilter(e.scope);return e.additionalQuery&&t.push(e.additionalQuery),e.textQuery&&t.push(e.textQuery),{query:{bool:{filter:t}},size:0,aggregations:e.aggregations,timeout:`10s`}}function isIncompleteResponse(e){let t=e;return t?.timed_out===!0||(t?._shards?.failed??0)>0}const asClientError=e=>typeof e==`object`&&e?e:void 0;function isIndexNotFoundError(e){return asClientError(e)?.meta?.body?.error?.type===`index_not_found_exception`}function isOpenSearchBadRequest(e){return asClientError(e)?.meta?.statusCode===400}function describeOpenSearchRejection(e,t){let n=e.meta?.body?.error,r=n?.root_cause?.[0]?.reason??n?.reason??e.message??`unknown`;return{message:`The search backend rejected the synced query: ${r}. Check the ${t.map(e=>`'${e}'`).join(` and `)} arguments against the queryable fields`,parameters:t,reason:r}}exports.AGGREGATION_TIMEOUT=`10s`,exports.DEFAULT_SYNCED_PAGE_SIZE=100,exports.MAX_SYNCED_PAGE_SIZE=200,exports.MAX_SYNCED_RESULT_WINDOW=M,exports.SEARCH_TIMEOUT=`10s`,exports.SUPPORTED_QUERY_OPERATIONS=t,exports.SYNCED_FILTER_GRAMMAR=n,exports.SYNCED_SEARCH_DESCRIPTION='Free-text search over the record\'s text fields, ranked by relevance. Supports "quoted phrases", +required / -excluded terms, and prefix* — lenient (never errors). Combine with `filter` to narrow; results are relevance-ranked unless an explicit sort is given.',exports.SyncedQueryRejection=SyncedQueryRejection,exports.assertPageWindow=assertPageWindow,exports.buildAggregationBody=buildAggregationBody,exports.buildFullTextQuery=buildFullTextQuery,exports.buildSearchBody=buildSearchBody,exports.buildTenantScopeFilter=buildTenantScopeFilter,exports.collectFieldValues=collectFieldValues,exports.describeOpenSearchRejection=describeOpenSearchRejection,exports.determineFieldType=determineFieldType,exports.findNextAvailableSlot=findNextAvailableSlot,exports.flattenItem=flattenItem,exports.inferPrimitiveType=inferPrimitiveType,exports.isIncompleteResponse=isIncompleteResponse,exports.isIndexNotFoundError=isIndexNotFoundError,exports.isOpenSearchBadRequest=isOpenSearchBadRequest,exports.resolveField=resolveField,exports.syncedQueryError=syncedQueryError,exports.translateAggs=translateAggs,exports.translateQuery=translateQuery,exports.translateSortFields=translateSortFields,exports.translateSourceFields=translateSourceFields,exports.unflattenItem=unflattenItem;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//#endregion
|
|
2
|
+
//#region src/documents.d.ts
|
|
3
|
+
type FieldType = 'string' | 'int' | 'float' | 'bool' | 'date';
|
|
4
|
+
declare function collectFieldValues(items: Record<string, unknown>[]): Map<string, unknown[]>;
|
|
5
|
+
declare function flattenItem(item: Record<string, unknown>): Record<string, unknown>;
|
|
6
|
+
declare function unflattenItem(flat: Record<string, unknown>): Record<string, unknown>;
|
|
7
|
+
declare function inferPrimitiveType(value: unknown): FieldType | null;
|
|
8
|
+
declare function determineFieldType(values: unknown[]): FieldType | null;
|
|
9
|
+
declare function findNextAvailableSlot(type: FieldType, usedSlots: Set<string>): string;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/aggregations.d.ts
|
|
12
|
+
declare const translatedAggregations: unique symbol;
|
|
13
|
+
type TranslatedAggregations = Record<string, unknown> & {
|
|
14
|
+
readonly [translatedAggregations]: true;
|
|
15
|
+
};
|
|
16
|
+
declare function translateAggs(aggs: Record<string, unknown>, aliasMap: Map<string, string>, fieldTypes?: Map<string, FieldType>): TranslatedAggregations;
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/errors.d.ts
|
|
19
|
+
type SyncedQueryParameter = 'filter' | 'sort' | 'source' | 'aggs' | 'page_size' | 'skip';
|
|
20
|
+
type SyncedQueryRejectionDetails = {
|
|
21
|
+
parameter: SyncedQueryParameter;
|
|
22
|
+
} & Record<string, unknown>;
|
|
23
|
+
declare class SyncedQueryRejection extends Error {
|
|
24
|
+
readonly details: SyncedQueryRejectionDetails;
|
|
25
|
+
constructor(message: string, details: SyncedQueryRejectionDetails);
|
|
26
|
+
toResponse(): Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
declare const syncedQueryError: (message: string, details: SyncedQueryRejectionDetails) => SyncedQueryRejection;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/limits.d.ts
|
|
31
|
+
declare const DEFAULT_SYNCED_PAGE_SIZE = 100;
|
|
32
|
+
declare const MAX_SYNCED_PAGE_SIZE = 200;
|
|
33
|
+
declare const MAX_SYNCED_RESULT_WINDOW = 10000;
|
|
34
|
+
declare function assertPageWindow(pageSize: number | undefined, skip: number | undefined): void;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/query.d.ts
|
|
37
|
+
declare const translatedQuery: unique symbol;
|
|
38
|
+
declare const translatedSource: unique symbol;
|
|
39
|
+
declare const translatedSort: unique symbol;
|
|
40
|
+
declare const fullTextQuery: unique symbol;
|
|
41
|
+
type TranslatedQuery = Record<string, unknown> & {
|
|
42
|
+
readonly [translatedQuery]: true;
|
|
43
|
+
};
|
|
44
|
+
type TranslatedSource = string[] & {
|
|
45
|
+
readonly [translatedSource]: true;
|
|
46
|
+
};
|
|
47
|
+
type TranslatedSort = unknown[] & {
|
|
48
|
+
readonly [translatedSort]: true;
|
|
49
|
+
};
|
|
50
|
+
type FullTextQuery = Record<string, unknown> & {
|
|
51
|
+
readonly [fullTextQuery]: true;
|
|
52
|
+
};
|
|
53
|
+
declare const SUPPORTED_QUERY_OPERATIONS: readonly ["term", "terms", "range", "wildcard", "match", "match_phrase", "exists", "bool"];
|
|
54
|
+
declare const SYNCED_FILTER_GRAMMAR: string;
|
|
55
|
+
declare const SYNCED_SEARCH_DESCRIPTION: string;
|
|
56
|
+
declare function buildFullTextQuery(search: string): FullTextQuery | undefined;
|
|
57
|
+
declare const resolveField: (field: string, aliasMap: Map<string, string>, parameter?: SyncedQueryParameter) => string;
|
|
58
|
+
declare const translateQuery: (query: Record<string, unknown>, aliasMap: Map<string, string>, fieldTypes?: Map<string, FieldType>) => TranslatedQuery;
|
|
59
|
+
declare function translateSourceFields(fields: string[], aliasMap: Map<string, string>): TranslatedSource;
|
|
60
|
+
declare function translateSortFields(sort: unknown[], aliasMap: Map<string, string>): TranslatedSort;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/opensearch.d.ts
|
|
63
|
+
declare const SEARCH_TIMEOUT = "10s";
|
|
64
|
+
declare const AGGREGATION_TIMEOUT = "10s";
|
|
65
|
+
type TenantScope = {
|
|
66
|
+
projectId: number;
|
|
67
|
+
accountId: string;
|
|
68
|
+
actionId: string;
|
|
69
|
+
paramsHash: string;
|
|
70
|
+
lastFullRunId: string;
|
|
71
|
+
ref?: string;
|
|
72
|
+
};
|
|
73
|
+
declare function buildTenantScopeFilter(scope: TenantScope): Record<string, unknown>[];
|
|
74
|
+
type SearchBodyArgs = {
|
|
75
|
+
scope: TenantScope;
|
|
76
|
+
additionalQuery?: TranslatedQuery;
|
|
77
|
+
textQuery?: FullTextQuery;
|
|
78
|
+
source?: TranslatedSource;
|
|
79
|
+
sort?: TranslatedSort;
|
|
80
|
+
size?: number;
|
|
81
|
+
from?: number;
|
|
82
|
+
};
|
|
83
|
+
declare function buildSearchBody(args: SearchBodyArgs): Record<string, unknown>;
|
|
84
|
+
type AggregationBodyArgs = {
|
|
85
|
+
scope: TenantScope;
|
|
86
|
+
aggregations: TranslatedAggregations;
|
|
87
|
+
additionalQuery?: TranslatedQuery;
|
|
88
|
+
textQuery?: FullTextQuery;
|
|
89
|
+
};
|
|
90
|
+
declare function buildAggregationBody(args: AggregationBodyArgs): Record<string, unknown>;
|
|
91
|
+
declare function isIncompleteResponse(body: unknown): boolean;
|
|
92
|
+
type OpenSearchClientError = {
|
|
93
|
+
message?: string;
|
|
94
|
+
meta?: {
|
|
95
|
+
statusCode?: number;
|
|
96
|
+
body?: {
|
|
97
|
+
error?: {
|
|
98
|
+
type?: string;
|
|
99
|
+
reason?: string;
|
|
100
|
+
root_cause?: {
|
|
101
|
+
reason?: string;
|
|
102
|
+
}[];
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
declare function isIndexNotFoundError(error: unknown): boolean;
|
|
108
|
+
declare function isOpenSearchBadRequest(error: unknown): error is OpenSearchClientError;
|
|
109
|
+
declare function describeOpenSearchRejection(error: OpenSearchClientError, parameters: string[]): {
|
|
110
|
+
message: string;
|
|
111
|
+
parameters: string[];
|
|
112
|
+
reason: string;
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
export { AGGREGATION_TIMEOUT, type AggregationBodyArgs, DEFAULT_SYNCED_PAGE_SIZE, type FieldType, type FullTextQuery, MAX_SYNCED_PAGE_SIZE, MAX_SYNCED_RESULT_WINDOW, type OpenSearchClientError, SEARCH_TIMEOUT, SUPPORTED_QUERY_OPERATIONS, SYNCED_FILTER_GRAMMAR, SYNCED_SEARCH_DESCRIPTION, type SearchBodyArgs, type SyncedQueryParameter, SyncedQueryRejection, type SyncedQueryRejectionDetails, type TenantScope, type TranslatedAggregations, type TranslatedQuery, type TranslatedSort, type TranslatedSource, assertPageWindow, buildAggregationBody, buildFullTextQuery, buildSearchBody, buildTenantScopeFilter, collectFieldValues, describeOpenSearchRejection, determineFieldType, findNextAvailableSlot, flattenItem, inferPrimitiveType, isIncompleteResponse, isIndexNotFoundError, isOpenSearchBadRequest, resolveField, syncedQueryError, translateAggs, translateQuery, translateSortFields, translateSourceFields, unflattenItem };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { t as __name } from "./chunk-BhWHdk3m.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/documents.d.ts
|
|
4
|
+
type FieldType = 'string' | 'int' | 'float' | 'bool' | 'date';
|
|
5
|
+
declare function collectFieldValues(items: Record<string, unknown>[]): Map<string, unknown[]>;
|
|
6
|
+
declare function flattenItem(item: Record<string, unknown>): Record<string, unknown>;
|
|
7
|
+
declare function unflattenItem(flat: Record<string, unknown>): Record<string, unknown>;
|
|
8
|
+
declare function inferPrimitiveType(value: unknown): FieldType | null;
|
|
9
|
+
declare function determineFieldType(values: unknown[]): FieldType | null;
|
|
10
|
+
declare function findNextAvailableSlot(type: FieldType, usedSlots: Set<string>): string;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/aggregations.d.ts
|
|
13
|
+
declare const translatedAggregations: unique symbol;
|
|
14
|
+
type TranslatedAggregations = Record<string, unknown> & {
|
|
15
|
+
readonly [translatedAggregations]: true;
|
|
16
|
+
};
|
|
17
|
+
declare function translateAggs(aggs: Record<string, unknown>, aliasMap: Map<string, string>, fieldTypes?: Map<string, FieldType>): TranslatedAggregations;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/errors.d.ts
|
|
20
|
+
type SyncedQueryParameter = 'filter' | 'sort' | 'source' | 'aggs' | 'page_size' | 'skip';
|
|
21
|
+
type SyncedQueryRejectionDetails = {
|
|
22
|
+
parameter: SyncedQueryParameter;
|
|
23
|
+
} & Record<string, unknown>;
|
|
24
|
+
declare class SyncedQueryRejection extends Error {
|
|
25
|
+
readonly details: SyncedQueryRejectionDetails;
|
|
26
|
+
constructor(message: string, details: SyncedQueryRejectionDetails);
|
|
27
|
+
toResponse(): Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
declare const syncedQueryError: (message: string, details: SyncedQueryRejectionDetails) => SyncedQueryRejection;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/limits.d.ts
|
|
32
|
+
declare const DEFAULT_SYNCED_PAGE_SIZE = 100;
|
|
33
|
+
declare const MAX_SYNCED_PAGE_SIZE = 200;
|
|
34
|
+
declare const MAX_SYNCED_RESULT_WINDOW = 10000;
|
|
35
|
+
declare function assertPageWindow(pageSize: number | undefined, skip: number | undefined): void;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/query.d.ts
|
|
38
|
+
declare const translatedQuery: unique symbol;
|
|
39
|
+
declare const translatedSource: unique symbol;
|
|
40
|
+
declare const translatedSort: unique symbol;
|
|
41
|
+
declare const fullTextQuery: unique symbol;
|
|
42
|
+
type TranslatedQuery = Record<string, unknown> & {
|
|
43
|
+
readonly [translatedQuery]: true;
|
|
44
|
+
};
|
|
45
|
+
type TranslatedSource = string[] & {
|
|
46
|
+
readonly [translatedSource]: true;
|
|
47
|
+
};
|
|
48
|
+
type TranslatedSort = unknown[] & {
|
|
49
|
+
readonly [translatedSort]: true;
|
|
50
|
+
};
|
|
51
|
+
type FullTextQuery = Record<string, unknown> & {
|
|
52
|
+
readonly [fullTextQuery]: true;
|
|
53
|
+
};
|
|
54
|
+
declare const SUPPORTED_QUERY_OPERATIONS: readonly ["term", "terms", "range", "wildcard", "match", "match_phrase", "exists", "bool"];
|
|
55
|
+
declare const SYNCED_FILTER_GRAMMAR: string;
|
|
56
|
+
declare const SYNCED_SEARCH_DESCRIPTION: string;
|
|
57
|
+
declare function buildFullTextQuery(search: string): FullTextQuery | undefined;
|
|
58
|
+
declare const resolveField: (field: string, aliasMap: Map<string, string>, parameter?: SyncedQueryParameter) => string;
|
|
59
|
+
declare const translateQuery: (query: Record<string, unknown>, aliasMap: Map<string, string>, fieldTypes?: Map<string, FieldType>) => TranslatedQuery;
|
|
60
|
+
declare function translateSourceFields(fields: string[], aliasMap: Map<string, string>): TranslatedSource;
|
|
61
|
+
declare function translateSortFields(sort: unknown[], aliasMap: Map<string, string>): TranslatedSort;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/opensearch.d.ts
|
|
64
|
+
declare const SEARCH_TIMEOUT = "10s";
|
|
65
|
+
declare const AGGREGATION_TIMEOUT = "10s";
|
|
66
|
+
type TenantScope = {
|
|
67
|
+
projectId: number;
|
|
68
|
+
accountId: string;
|
|
69
|
+
actionId: string;
|
|
70
|
+
paramsHash: string;
|
|
71
|
+
lastFullRunId: string;
|
|
72
|
+
ref?: string;
|
|
73
|
+
};
|
|
74
|
+
declare function buildTenantScopeFilter(scope: TenantScope): Record<string, unknown>[];
|
|
75
|
+
type SearchBodyArgs = {
|
|
76
|
+
scope: TenantScope;
|
|
77
|
+
additionalQuery?: TranslatedQuery;
|
|
78
|
+
textQuery?: FullTextQuery;
|
|
79
|
+
source?: TranslatedSource;
|
|
80
|
+
sort?: TranslatedSort;
|
|
81
|
+
size?: number;
|
|
82
|
+
from?: number;
|
|
83
|
+
};
|
|
84
|
+
declare function buildSearchBody(args: SearchBodyArgs): Record<string, unknown>;
|
|
85
|
+
type AggregationBodyArgs = {
|
|
86
|
+
scope: TenantScope;
|
|
87
|
+
aggregations: TranslatedAggregations;
|
|
88
|
+
additionalQuery?: TranslatedQuery;
|
|
89
|
+
textQuery?: FullTextQuery;
|
|
90
|
+
};
|
|
91
|
+
declare function buildAggregationBody(args: AggregationBodyArgs): Record<string, unknown>;
|
|
92
|
+
declare function isIncompleteResponse(body: unknown): boolean;
|
|
93
|
+
type OpenSearchClientError = {
|
|
94
|
+
message?: string;
|
|
95
|
+
meta?: {
|
|
96
|
+
statusCode?: number;
|
|
97
|
+
body?: {
|
|
98
|
+
error?: {
|
|
99
|
+
type?: string;
|
|
100
|
+
reason?: string;
|
|
101
|
+
root_cause?: {
|
|
102
|
+
reason?: string;
|
|
103
|
+
}[];
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
declare function isIndexNotFoundError(error: unknown): boolean;
|
|
109
|
+
declare function isOpenSearchBadRequest(error: unknown): error is OpenSearchClientError;
|
|
110
|
+
declare function describeOpenSearchRejection(error: OpenSearchClientError, parameters: string[]): {
|
|
111
|
+
message: string;
|
|
112
|
+
parameters: string[];
|
|
113
|
+
reason: string;
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
export { AGGREGATION_TIMEOUT, type AggregationBodyArgs, DEFAULT_SYNCED_PAGE_SIZE, type FieldType, type FullTextQuery, MAX_SYNCED_PAGE_SIZE, MAX_SYNCED_RESULT_WINDOW, type OpenSearchClientError, SEARCH_TIMEOUT, SUPPORTED_QUERY_OPERATIONS, SYNCED_FILTER_GRAMMAR, SYNCED_SEARCH_DESCRIPTION, type SearchBodyArgs, type SyncedQueryParameter, SyncedQueryRejection, type SyncedQueryRejectionDetails, type TenantScope, type TranslatedAggregations, type TranslatedQuery, type TranslatedSort, type TranslatedSource, assertPageWindow, buildAggregationBody, buildFullTextQuery, buildSearchBody, buildTenantScopeFilter, collectFieldValues, describeOpenSearchRejection, determineFieldType, findNextAvailableSlot, flattenItem, inferPrimitiveType, isIncompleteResponse, isIndexNotFoundError, isOpenSearchBadRequest, resolveField, syncedQueryError, translateAggs, translateQuery, translateSortFields, translateSourceFields, unflattenItem };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./chunk-BhWHdk3m.mjs";var SyncedQueryRejection=class extends Error{constructor(e,t){super(e),this.name=`SyncedQueryRejection`,this.details=t}toResponse(){return{...this.details,message:this.message,error:`Bad Request`,statusCode:400}}};const syncedQueryError=(e,t)=>new SyncedQueryRejection(e,t),t=[`term`,`terms`,`range`,`wildcard`,`match`,`match_phrase`,`exists`,`bool`],n=`OpenSearch-style filter (Query DSL) (${t.join(` / `)}) over synced fields. A filter is one clause: an object with exactly one query type, keyed by field name, e.g. {"term": {"<field>": <value>}}, {"terms": {"<field>": [...]}}, {"range": {"<field>": {"gte": ...}}}; combine clauses with {"bool": {"filter": [...], "must_not": [...]}}. Bare {"<field>": <value>} maps are rejected. match / match_phrase do word-level search (string fields only, case-insensitive); wildcard matches patterns (case-sensitive).`,r='Free-text search over the record\'s text fields, ranked by relevance. Supports "quoted phrases", +required / -excluded terms, and prefix* — lenient (never errors). Combine with `filter` to narrow; results are relevance-ranked unless an explicit sort is given.';function buildFullTextQuery(e){let t=e.trim();if(t)return{simple_query_string:{query:t,fields:[`data.*.text`]}}}const i=e(e=>typeof e==`object`&&!!e&&!Array.isArray(e),`isPlainObject`),isScalar=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,isStringOrNumber=e=>typeof e==`string`||typeof e==`number`,isNumeric=e=>typeof e==`number`||typeof e==`string`&&e.trim()!==``&&Number.isFinite(Number(e)),describeValue=e=>e===null?`null`:Array.isArray(e)?`an array`:`a ${typeof e}`,describeAvailableFields=e=>{let t=[...e.keys()];if(t.length===0)return`No fields are queryable for this sync yet`;let n=t.slice(0,50),r=t.length-n.length,i=r>0?`, … and ${r} more`:``;return`Available fields: ${n.join(`, `)}${i}`},resolveField=(e,t,n=`filter`)=>{let r=t.get(e);if(!r)throw syncedQueryError(`Field '${e}' is not available for querying. ${describeAvailableFields(t)}`,{parameter:n,field:e,available_fields:[...t.keys()]});return`data.${r}`},filterError=(e,t)=>syncedQueryError(e,{parameter:`filter`,path:t}),a=new Set([`term`,`terms`,`range`,`wildcard`]),o=new Set([`match`,`match_phrase`]),s=new Set([`must`,`should`,`filter`,`must_not`]),c=new Set([`minimum_should_match`]),l=[`value`,`boost`,`case_insensitive`],u=[`value`,`boost`,`case_insensitive`,`rewrite`],d=[`query`,`operator`,`minimum_should_match`,`fuzziness`,`prefix_length`,`max_expansions`,`zero_terms_query`,`boost`],f=[`query`,`slop`,`zero_terms_query`,`boost`],p=new Set([`gt`,`gte`,`lt`,`lte`]),m=new Set([`format`,`time_zone`]),unsupportedQueryType=(e,n,r)=>{let i=`Supported types: ${t.join(`, `)}`;return n.aliasMap.has(e)?filterError(`'${e}' is a queryable field, not a query type. Wrap it in a clause, e.g. {"term": {"${e}": ...}}. ${i}`,r):filterError(`Unsupported query type '${e}'. ${i}`,r)},assertSupportedOptions=(e,t,n,r)=>{let i=Object.keys(t).find(e=>!n.includes(e));if(i!==void 0)throw filterError(`'${e}' option '${i}' is not supported. Supported: ${n.join(`, `)}`,r)},validateTermValue=(e,t,n)=>{if(!isScalar(t)){if(i(t)&&isScalar(t.value)){assertSupportedOptions(`term`,t,l,n);return}throw filterError(`'term' on '${e}' must be a single string, number or boolean (or {"value": ...}); use 'terms' for a list of values`,n)}},validateTermsValue=(e,t,n)=>{if(!(Array.isArray(t)&&t.every(isScalar)))throw filterError(`'terms' on '${e}' must be an array of values`,n)},validateWildcardValue=(e,t,n,r)=>{if(n!==void 0&&n!==`string`)throw filterError(`'wildcard' is only supported on string fields; '${e}' is not a string field`,r);if(typeof t!=`string`){if(i(t)&&typeof t.value==`string`){assertSupportedOptions(`wildcard`,t,u,r);return}throw filterError(`'wildcard' on '${e}' must be a pattern string (or {"value": "<pattern>"})`,r)}},validateTextValue=(e,t,n,r)=>{if(typeof n!=`string`){if(i(n)&&isScalar(n.query)){assertSupportedOptions(e,n,e===`match`?d:f,r);return}throw filterError(`'${e}' on '${t}' must be a string or a { "query": ... } object`,r)}},validateRangeBound=(e,t,n,r,i)=>{if(!isStringOrNumber(n))throw filterError(`'range' bound '${t}' on '${e}' must be a string or number`,i);if((r===`int`||r===`float`)&&!isNumeric(n))throw filterError(`'range' bound '${t}' on numeric field '${e}' must be a number`,i);if(r===`date`&&typeof n==`boolean`)throw filterError(`'range' bound '${t}' on date field '${e}' must be an ISO date string, date-math expression or epoch millis`,i)},validateRangeValue=(e,t,n,r)=>{if(n===`bool`)throw filterError(`'range' is not supported on boolean field '${e}'`,r);if(!i(t))throw filterError(`'range' on '${e}' must be an object of bounds, e.g. {"gte": ..., "lte": ...}`,r);let a=Object.keys(t);for(let i of a){if(p.has(i)){validateRangeBound(e,i,t[i],n,r);continue}if(m.has(i)){if(n!==void 0&&n!==`date`)throw filterError(`'range' option '${i}' is only supported on date fields; '${e}' is a ${n} field`,r);continue}throw filterError(`'range' operator '${i}' is not supported. Supported: ${[...p].join(`, `)} (plus ${[...m].join(`, `)} on date fields)`,r)}if(!a.some(e=>p.has(e)))throw filterError(`'range' on '${e}' needs at least one bound (${[...p].join(`, `)})`,r)},translateFieldClause=(e,t,n,r)=>{if(!i(t))throw filterError(`'${e}' clause at '${r}' must be an object keyed by field name, e.g. {"${e}": {"<field>": ...}}`,r);let a=Object.keys(t);if(a.length!==1)throw filterError(`'${e}' clause at '${r}' must name exactly one field, got: ${a.join(`, `)||`none`}`,r);let[s]=a,c=t[s],l=`${r}.${s}`,u=resolveField(s,n.aliasMap),d=n.fieldTypes.get(s);if(o.has(e)){if(d!==`string`)throw filterError(`'${e}' is only supported on string fields; '${s}' is not a string field`,l);return validateTextValue(e,s,c,l),{[e]:{[`${u}.text`]:c}}}return e===`term`?validateTermValue(s,c,l):e===`terms`?validateTermsValue(s,c,l):e===`range`?validateRangeValue(s,c,d,l):validateWildcardValue(s,c,d,l),{[e]:{[u]:c}}},translateExists=(e,t,n)=>{if(!i(e)||typeof e.field!=`string`)throw filterError(`'exists' query requires a string 'field'`,n);let r=Object.keys(e).filter(e=>e!==`field`);if(r.length>0)throw filterError(`'exists' only accepts 'field'; unsupported: ${r.join(`, `)}`,n);return{field:resolveField(e.field,t.aliasMap)}},translateBool=(e,t,n,r)=>{if(!i(e))throw filterError(`'bool' clause at '${n}' must be an object of clauses (${[...s].join(`, `)})`,n);let a={};for(let[i,o]of Object.entries(e)){if(s.has(i)){a[i]=(Array.isArray(o)?o:[o]).map((e,a)=>translateNode(e,t,`${n}.${i}[${a}]`,r+1));continue}if(c.has(i)){if(typeof o!=`number`&&typeof o!=`string`)throw filterError(`'bool' option '${i}' must be a number or string`,n);a[i]=o;continue}throw filterError(`'bool' option '${i}' is not supported. Supported: ${[...s,...c].join(`, `)}`,n)}return a},translateNode=(e,t,n,r)=>{if(r>10)throw filterError(`Filter nesting too deep (maximum 10 levels)`,n);if(!i(e))throw filterError(`Filter clause at '${n}' must be an object, got ${describeValue(e)}`,n);let s=Object.keys(e);if(s.length===0)throw filterError(`Filter clause at '${n}' is empty`,n);if(s.length>1)throw filterError(`Filter clause at '${n}' must contain exactly one query type, got: ${s.join(`, `)}. Combine clauses with {"bool": {"filter": [...]}}`,n);let[c]=s,l=e[c],u=`${n}.${c}`;if(c===`bool`)return{bool:translateBool(l,t,u,r)};if(c===`exists`)return{exists:translateExists(l,t,u)};if(a.has(c)||o.has(c))return translateFieldClause(c,l,t,u);throw unsupportedQueryType(c,t,n)},translateQuery=(e,t,n=new Map)=>translateNode(e,{aliasMap:t,fieldTypes:n},`filter`,0);function translateSourceFields(e,t){return e.map(e=>resolveField(e,t,`source`))}const h=new Set([`asc`,`desc`]),g=new Set([`min`,`max`,`sum`,`avg`,`median`]),_=[`order`,`missing`,`mode`],sortError=e=>syncedQueryError(e,{parameter:`sort`}),validateSortOptions=(e,t)=>{let n=sortError(`'sort' order for '${e}' must be "asc" or "desc"`);if(typeof t==`string`){if(!h.has(t))throw n;return}if(!i(t))throw sortError(`'sort' options for '${e}' must be "asc", "desc" or an object like {"order": "desc"}`);let r=Object.keys(t).find(e=>!_.includes(e));if(r!==void 0)throw sortError(`'sort' option '${r}' is not supported. Supported: ${_.join(`, `)}`);if(`order`in t&&!(typeof t.order==`string`&&h.has(t.order)))throw n;if(`mode`in t&&!(typeof t.mode==`string`&&g.has(t.mode)))throw sortError(`'sort' mode for '${e}' must be one of ${[...g].join(`, `)}`)};function translateSortFields(e,t){return e.map(e=>{if(typeof e==`string`)return resolveField(e,t,`sort`);if(i(e)){let n=Object.keys(e);if(n.length!==1)throw sortError(`Each sort entry must reference exactly one field`);let[r]=n,i=resolveField(r,t,`sort`);return validateSortOptions(r,e[r]),{[i]:e[r]}}throw sortError(`Each sort entry must be a field name or a '{ field: order }' object`)})}const v=1e3,y=[`avg`,`sum`,`min`,`max`,`value_count`,`stats`,`extended_stats`,`cardinality`,`percentiles`],b=[`terms`,`date_histogram`,`histogram`,`range`,`date_range`],x=new Set([...y,...b]),S=new Set([`__proto__`,`constructor`,`prototype`]),C=[`int`,`float`],w={avg:{label:`a numeric`,types:C},sum:{label:`a numeric`,types:C},stats:{label:`a numeric`,types:C},extended_stats:{label:`a numeric`,types:C},percentiles:{label:`a numeric`,types:C},histogram:{label:`a numeric`,types:C},range:{label:`a numeric`,types:C},min:{label:`a numeric or date`,types:[...C,`date`]},max:{label:`a numeric or date`,types:[...C,`date`]},date_histogram:{label:`a date`,types:[`date`]},date_range:{label:`a date`,types:[`date`]}},T=[`missing`,`format`],E={avg:T,sum:T,min:T,max:T,value_count:T,stats:T,extended_stats:T,cardinality:[`precision_threshold`,`missing`,`format`],percentiles:[`percents`,`keyed`,`missing`,`format`],terms:[`size`,`missing`,`min_doc_count`,`order`],date_histogram:[`calendar_interval`,`fixed_interval`,`interval`,`offset`,`time_zone`,`format`,`min_doc_count`,`order`,`missing`,`keyed`],histogram:[`interval`,`offset`,`min_doc_count`,`order`,`missing`,`keyed`],range:[`ranges`,`keyed`,`missing`],date_range:[`ranges`,`format`,`time_zone`,`keyed`,`missing`]},D=new Set([`aggs`,`aggregations`]),O={date_histogram:{keys:[`calendar_interval`,`fixed_interval`,`interval`],exactlyOne:!0},histogram:{keys:[`interval`]},range:{keys:[`ranges`]},date_range:{keys:[`ranges`]}};function isPlainObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}const k=new Set([`from`,`to`,`key`]);function rangeEntryKeys(e){return Object.keys(e).every(e=>k.has(e))}const aggsError=e=>syncedQueryError(e,{parameter:`aggs`});function assertAggFieldType(e,t,n){let r=w[e],i=n.fieldTypes.get(t);if(!(!r||i===void 0||r.types.includes(i)))throw syncedQueryError(`'${e}' requires ${r.label} field; '${t}' is a ${i} field`,{parameter:`aggs`,field:t})}function translateAggBody(e,t,n){if(!isPlainObject(t))throw aggsError(`Aggregation definition must be an object`);let r=E[e]??[],i={};for(let[a,o]of Object.entries(t)){if(a===`field`){if(typeof o!=`string`)throw aggsError(`Aggregation 'field' must be a string`);i[a]=resolveField(o,n.aliasMap,`aggs`),assertAggFieldType(e,o,n);continue}if(!r.includes(a))throw aggsError(`Aggregation option '${a}' is not supported for '${e}'. Supported: field, ${r.join(`, `)}`);if(a===`size`){if(typeof o!=`number`||!Number.isInteger(o)||o<1)throw aggsError(`Aggregation 'size' must be a positive integer`);if(o>v)throw aggsError(`Aggregation 'size' exceeds the maximum of ${v}`)}if(a===`ranges`){if(!Array.isArray(o)||o.length===0||!o.every(e=>isPlainObject(e)&&rangeEntryKeys(e)))throw aggsError(`Aggregation 'ranges' must be an array of {from, to, key} objects`);if(o.length>100)throw aggsError(`Aggregation 'ranges' exceeds the maximum of 100 entries`)}if(a===`percents`){if(!Array.isArray(o)||o.length===0||!o.every(e=>typeof e==`number`&&Number.isFinite(e)&&e>=0&&e<=100))throw aggsError(`Aggregation 'percents' must be an array of numbers between 0 and 100`);if(o.length>100)throw aggsError(`Aggregation 'percents' exceeds the maximum of 100 entries`)}if(a===`interval`){if(e===`date_histogram`){if(typeof o!=`string`||o.length===0)throw aggsError(`Aggregation 'interval' must be a non-empty string`)}else if(typeof o!=`number`||!Number.isFinite(o)||o<=0)throw aggsError(`Aggregation 'interval' must be a finite number greater than 0`)}if((a===`fixed_interval`||a===`calendar_interval`)&&(typeof o!=`string`||o.length===0))throw aggsError(`Aggregation '${a}' must be a non-empty string`);if(a===`min_doc_count`&&(typeof o!=`number`||!Number.isInteger(o)||o<0))throw aggsError(`Aggregation 'min_doc_count' must be a non-negative integer`);if(a===`keyed`&&typeof o!=`boolean`)throw aggsError(`Aggregation 'keyed' must be a boolean`);i[a]=o}if(!(`field`in t))throw aggsError(`Aggregation '${e}' requires a 'field'`);let a=O[e];if(a){let n=a.keys.filter(e=>e in t);if(a.exactlyOne&&n.length!==1)throw aggsError(`Aggregation '${e}' requires exactly one of: ${a.keys.join(`, `)}`);if(!a.exactlyOne&&n.length===0)throw a.keys.length===1?aggsError(`Aggregation '${e}' requires '${a.keys[0]}'`):aggsError(`Aggregation '${e}' requires one of: ${a.keys.join(`, `)}`)}return i}function translateAggDefinition(e,t,n,r){if(!isPlainObject(t))throw aggsError(`Aggregation definition must be an object`);let i=Object.keys(t).filter(e=>e!==`meta`&&!D.has(e));if(i.length!==1)throw aggsError(`Aggregation '${e}' must define exactly one aggregation type, got${i.length===0?` none`:`: ${i.join(`, `)}`}`);let a={};for(let[e,i]of Object.entries(t)){if(D.has(e)){a[e]=translateAggMap(i,n,r+1);continue}if(e===`meta`){a[e]=i;continue}if(!x.has(e))throw aggsError(`Aggregation type '${e}' is not supported. Supported types: ${[...x].join(`, `)}`);a[e]=translateAggBody(e,i,n)}return a}function translateAggMap(e,t,n){if(n>5)throw aggsError(`Aggregation nesting too deep`);if(!isPlainObject(e))throw aggsError(`Aggregations must be an object`);let r={};for(let[i,a]of Object.entries(e)){if(S.has(i))throw aggsError(`Aggregation name '${i}' is reserved`);if(t.definitions+=1,t.definitions>50)throw aggsError(`Aggregations exceed the maximum of 50 definitions`);r[i]=translateAggDefinition(i,a,t,n)}return r}function translateAggs(e,t,n=new Map){return translateAggMap(e,{aliasMap:t,fieldTypes:n,definitions:0},0)}const A=.9,j=new Set([`__proto__`,`constructor`,`prototype`]),hasForbiddenSegment=e=>e.replace(/\[\]/g,``).split(`.`).some(e=>j.has(e));function processValue(e,t,n,r){if(t!=null&&!(n>=5)){if(Array.isArray(t)){let i=t.filter(e=>e!=null);if(i.length===0)return;let a=i[0];if(typeof a==`object`&&!Array.isArray(a))for(let t of i)processObject(`${e}[]`,t,n+2,r);else r.has(e)||r.set(e,[]),r.get(e)?.push(...i);return}if(typeof t==`object`){processObject(e,t,n+1,r);return}r.has(e)||r.set(e,[]),r.get(e)?.push(t)}}function processObject(e,t,n,r){if(!(n>=5))for(let[i,a]of Object.entries(t))j.has(i)||processValue(e?`${e}.${i}`:i,a,n,r)}function collectFieldValues(e){let t=new Map;for(let n of e)processObject(``,n,0,t);return t}function flattenItem(e){let t=new Map;return flattenObject(``,e,0,t),Object.fromEntries(t)}function flattenValue(e,t,n,r){if(t!=null&&!(n>=5)){if(Array.isArray(t)){if(t.length===0)return;let i=t.find(e=>e!=null);if(typeof i==`object`&&!Array.isArray(i))flattenArrayOfObjects(e,t,n,r);else{let n=t.filter(e=>e!=null);n.length>0&&r.set(e,n)}return}if(typeof t==`object`){flattenObject(e,t,n+1,r);return}r.set(e,t)}}function flattenObject(e,t,n,r){if(!(n>=5))for(let[i,a]of Object.entries(t))j.has(i)||flattenValue(e?`${e}.${i}`:i,a,n,r)}function flattenArrayOfObjects(e,t,n,r){let i=`${e}[]`,a=[],o=[],s=new Set;for(let e of t){if(typeof e!=`object`||!e||Array.isArray(e)){a.push(new Map);continue}let t=new Map;flattenObject(i,e,n+2,t),a.push(t);for(let e of t.keys())s.has(e)||(s.add(e),o.push(e))}for(let e of o){let n=t.map((t,n)=>{let r=a[n].get(e);return r===void 0?null:r});r.set(e,n)}}function unflattenItem(e){let t={},n=new Map;for(let[r,i]of Object.entries(e)){if(hasForbiddenSegment(r)||(r.match(/\[\]/g)??[]).length>5)continue;let e=r.indexOf(`[].`);if(e!==-1){let t=r.substring(0,e),a=r.substring(e+3);n.has(t)||n.set(t,new Map),n.get(t)?.set(a,i);continue}let a=r.split(`.`),o=t;for(let e=0;e<a.length-1;e++){let t=a[e];(!Object.prototype.hasOwnProperty.call(o,t)||typeof o[t]!=`object`||o[t]===null)&&(o[t]={}),o=o[t]}o[a[a.length-1]]=i}for(let[e,r]of n.entries()){let n=0;for(let e of r.values())Array.isArray(e)&&(n=Math.max(n,e.length));let i=[];for(let e=0;e<n;e++){let t={};for(let[n,i]of r.entries())t[n]=Array.isArray(i)?i[e]:i;i.push(unflattenItem(t))}let a=e.split(`.`),o=t;for(let e=0;e<a.length-1;e++){let t=a[e];(!Object.prototype.hasOwnProperty.call(o,t)||typeof o[t]!=`object`||o[t]===null)&&(o[t]={}),o=o[t]}o[a[a.length-1]]=i}return t}const M=/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;function isValidCalendarDate(e){let[t,n,r]=e.slice(0,10).split(`-`).map(Number);return n<1||n>12||r<1?!1:r<=new Date(Date.UTC(t,n,0)).getUTCDate()&&!Number.isNaN(Date.parse(e))}function inferPrimitiveType(e){return typeof e==`boolean`?`bool`:typeof e==`number`?Number.isInteger(e)?`int`:`float`:typeof e==`string`?M.test(e)&&isValidCalendarDate(e)?`date`:`string`:null}function determineFieldType(e){let t=e.map(inferPrimitiveType).filter(e=>e!==null);if(t.length===0)return null;let n=t.reduce((e,t)=>(e[t]=(e[t]??0)+1,e),{});if(((n.int??0)+(n.float??0))/t.length>=A)return(n.float??0)>0?`float`:`int`;let r=Object.entries(n).sort((e,t)=>t[1]-e[1])[0];return r[1]/t.length>=A?r[0]:`string`}function findNextAvailableSlot(e,t){let n=1;for(;t.has(`${e}${n}`);)n++;return`${e}${n}`}const N=100,P=200,F=1e4;function assertPageWindow(e,t){if(e!==void 0&&(!Number.isInteger(e)||e<1))throw syncedQueryError(`page_size ${e} must be an integer of at least 1`,{parameter:`page_size`,min_page_size:1});if(t!==void 0&&(!Number.isInteger(t)||t<0))throw syncedQueryError(`skip ${t} must be an integer of 0 or greater`,{parameter:`skip`,min_skip:0});if(e!==void 0&&e>200)throw syncedQueryError(`page_size ${e} exceeds the maximum of 200`,{parameter:`page_size`,max_page_size:200});let n=(t??0)+(e??100);if(n>1e4)throw syncedQueryError(`skip + page_size (${n}${e===void 0?`, page_size defaulting to 100`:``}) exceeds the maximum result window of ${F}; narrow the filter instead of paging deeper`,{parameter:`skip`,max_result_window:F})}const I=`10s`,L=`10s`;function buildTenantScopeFilter(e){let t=e.ref?[{term:{refs:e.ref}}]:[{term:{account_id:e.accountId}},{term:{params_hash:e.paramsHash}},{term:{last_full_run_id:e.lastFullRunId}}];return[{term:{project_id:e.projectId}},...t,{term:{action_id:e.actionId}}]}function buildSearchBody(e){let t=buildTenantScopeFilter(e.scope);e.additionalQuery&&t.push(e.additionalQuery);let n={filter:t};return e.textQuery&&(n.must=[e.textQuery]),{query:{bool:n},timeout:`10s`,size:e.size??100,from:e.from??0,...e.source?.length&&{_source:e.source},...e.sort?.length&&{sort:e.sort}}}function buildAggregationBody(e){let t=buildTenantScopeFilter(e.scope);return e.additionalQuery&&t.push(e.additionalQuery),e.textQuery&&t.push(e.textQuery),{query:{bool:{filter:t}},size:0,aggregations:e.aggregations,timeout:`10s`}}function isIncompleteResponse(e){let t=e;return t?.timed_out===!0||(t?._shards?.failed??0)>0}const asClientError=e=>typeof e==`object`&&e?e:void 0;function isIndexNotFoundError(e){return asClientError(e)?.meta?.body?.error?.type===`index_not_found_exception`}function isOpenSearchBadRequest(e){return asClientError(e)?.meta?.statusCode===400}function describeOpenSearchRejection(e,t){let n=e.meta?.body?.error,r=n?.root_cause?.[0]?.reason??n?.reason??e.message??`unknown`;return{message:`The search backend rejected the synced query: ${r}. Check the ${t.map(e=>`'${e}'`).join(` and `)} arguments against the queryable fields`,parameters:t,reason:r}}export{L as AGGREGATION_TIMEOUT,N as DEFAULT_SYNCED_PAGE_SIZE,P as MAX_SYNCED_PAGE_SIZE,F as MAX_SYNCED_RESULT_WINDOW,I as SEARCH_TIMEOUT,t as SUPPORTED_QUERY_OPERATIONS,n as SYNCED_FILTER_GRAMMAR,r as SYNCED_SEARCH_DESCRIPTION,SyncedQueryRejection,assertPageWindow,buildAggregationBody,buildFullTextQuery,buildSearchBody,buildTenantScopeFilter,collectFieldValues,describeOpenSearchRejection,determineFieldType,findNextAvailableSlot,flattenItem,inferPrimitiveType,isIncompleteResponse,isIndexNotFoundError,isOpenSearchBadRequest,resolveField,syncedQueryError,translateAggs,translateQuery,translateSortFields,translateSourceFields,unflattenItem};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stackone/datasync",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DataSync document model, s1query grammar and OpenSearch query builders",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.cts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"package.json",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": {
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"default": "./dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"require": {
|
|
20
|
+
"types": "./dist/index.d.cts",
|
|
21
|
+
"default": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [],
|
|
27
|
+
"author": "StackOne",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "rimraf dist",
|
|
31
|
+
"prebuild": "pnpm clean",
|
|
32
|
+
"build": "tsdown --env.NODE_ENV=production --minify",
|
|
33
|
+
"prebuild:dev": "pnpm clean",
|
|
34
|
+
"build:dev": "tsdown --env.NODE_ENV=development",
|
|
35
|
+
"code:format": "biome format ./src ./*.mjs",
|
|
36
|
+
"code:format:fix": "biome format --write ./src ./*.mjs",
|
|
37
|
+
"code:lint": "biome lint --error-on-warnings ./src ./*.mjs",
|
|
38
|
+
"code:lint:fix": "biome lint --write ./src ./*.mjs",
|
|
39
|
+
"code:check": "biome check ./src ./*.mjs",
|
|
40
|
+
"code:check:fix": "biome check --write ./src ./*.mjs",
|
|
41
|
+
"lint": "pnpm code:check",
|
|
42
|
+
"lint:fix": "pnpm code:check:fix",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest watch",
|
|
45
|
+
"test:typecheck": "tsc --noEmit --project tsconfig.tests.json",
|
|
46
|
+
"test:publint": "publint",
|
|
47
|
+
"publish-release": "pnpm publish --no-git-checks --access public"
|
|
48
|
+
}
|
|
49
|
+
}
|