@undefineds.co/xpod 0.2.43 → 0.2.45

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.
@@ -172,23 +172,25 @@ class FilterPushdownExtractor {
172
172
  }
173
173
  }
174
174
  // STRSTARTS, STRENDS, CONTAINS
175
- // Database stores serialized literals like "Alice" (with quotes)
176
- // So we adjust the search pattern accordingly:
177
- // - STRSTARTS('A') -> $startsWith: '"A' (add leading quote)
178
- // - STRENDS('e') -> $endsWith: 'e"' (add trailing quote)
179
- // - CONTAINS('x') -> $contains: 'x' (quotes don't affect middle content)
175
+ // Subject/predicate/graph columns store raw IRI strings. Object string
176
+ // operators are explicit lexical STR(?object) pushdowns so the store can
177
+ // route text literals to object_text and IRIs/blank nodes to object_key.
180
178
  if (['strstarts', 'strends', 'contains'].includes(op) && expr.args.length === 2) {
181
179
  const varName = (0, AlgebraUtils_1.extractStrVariable)(expr.args[0]);
182
180
  const value = (0, AlgebraUtils_1.extractLiteralValue)(expr.args[1]);
183
181
  if (varName && value && (0, AlgebraUtils_1.isVariableInPattern)(varName, pattern)) {
182
+ const position = (0, AlgebraUtils_1.getVariablePosition)(varName, pattern);
183
+ const startsWithOp = position === 'object' ? '$strStartsWith' : '$startsWith';
184
+ const endsWithOp = position === 'object' ? '$strEndsWith' : '$endsWith';
185
+ const containsOp = position === 'object' ? '$strContains' : '$contains';
184
186
  if (op === 'strstarts') {
185
- filters[varName] = { $startsWith: '"' + value };
187
+ filters[varName] = { [startsWithOp]: value };
186
188
  }
187
189
  else if (op === 'strends') {
188
- filters[varName] = { $endsWith: value + '"' };
190
+ filters[varName] = { [endsWithOp]: value };
189
191
  }
190
192
  else {
191
- filters[varName] = { $contains: value };
193
+ filters[varName] = { [containsOp]: value };
192
194
  }
193
195
  return filters;
194
196
  }
@@ -198,7 +200,10 @@ class FilterPushdownExtractor {
198
200
  const varName = (0, AlgebraUtils_1.extractStrVariable)(expr.args[0]);
199
201
  const regexPattern = (0, AlgebraUtils_1.extractLiteralValue)(expr.args[1]);
200
202
  if (varName && regexPattern && (0, AlgebraUtils_1.isVariableInPattern)(varName, pattern)) {
201
- filters[varName] = { $regex: regexPattern };
203
+ const position = (0, AlgebraUtils_1.getVariablePosition)(varName, pattern);
204
+ filters[varName] = position === 'object'
205
+ ? { $strRegex: regexPattern }
206
+ : { $regex: regexPattern };
202
207
  return filters;
203
208
  }
204
209
  }
@@ -293,7 +298,6 @@ class FilterPushdownExtractor {
293
298
  }
294
299
  }
295
300
  // LANGMATCHES(LANG(?x), "en") - language tagged literals end with @lang"
296
- // Serialization format: "value"@en
297
301
  if (op === 'langmatches' && expr.args.length === 2) {
298
302
  const langExpr = expr.args[0];
299
303
  const langPattern = expr.args[1];
@@ -303,6 +307,14 @@ class FilterPushdownExtractor {
303
307
  langExpr.args.length === 1) {
304
308
  const varName = (0, AlgebraUtils_1.extractVariable)(langExpr.args[0]);
305
309
  if (varName && (0, AlgebraUtils_1.isVariableInPattern)(varName, pattern)) {
310
+ const position = (0, AlgebraUtils_1.getVariablePosition)(varName, pattern);
311
+ if (position === 'object') {
312
+ if (langPattern.termType === 'Literal') {
313
+ const lang = langPattern.value.toLowerCase();
314
+ filters[varName] = { $language: lang };
315
+ return filters;
316
+ }
317
+ }
306
318
  // Get the language pattern
307
319
  if (langPattern.termType === 'Literal') {
308
320
  const lang = langPattern.value.toLowerCase();
@@ -349,7 +361,7 @@ class FilterPushdownExtractor {
349
361
  const rightTerm = (0, AlgebraUtils_1.extractTerm)(right);
350
362
  if (leftVarForTerm && rightTerm && (op === '=' || op === '!=')) {
351
363
  const filterOp = op === '=' ? '$eq' : '$ne';
352
- return { varName: leftVarForTerm, op: filterOp, value: (0, serialization_1.serializeObject)(rightTerm) };
364
+ return { varName: leftVarForTerm, op: filterOp, value: rightTerm };
353
365
  }
354
366
  return null;
355
367
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FilterPushdownExtractor.js","sourceRoot":"","sources":["../../../src/storage/sparql/FilterPushdownExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FilterPushdownExtractor - Extract pushdownable conditions from SPARQL FILTER\n * \n * Analyzes SPARQL FILTER expressions and extracts conditions that can be\n * pushed down to the database layer for efficient query execution.\n * \n * Supported pushdown operations:\n * - Comparison: =, !=, <, >, <=, >=\n * - String functions: STRSTARTS, STRENDS, CONTAINS, REGEX\n * - Type checking: isIRI, isBlank, isLiteral, isNumeric\n * - Language: LANGMATCHES\n * - Logical: AND, OR (with $in optimization)\n * - Set operations: IN, NOT IN\n * - Existence: BOUND, !BOUND\n */\n\nimport type { Algebra } from 'sparqlalgebrajs';\nimport { DataFactory } from 'rdf-data-factory';\n\nimport type { TermOperators } from '../quint/types';\nimport { fpEncode, NUMERIC_TYPES, DATETIME_TYPE, SEP, serializeObject } from '../quint/serialization';\nimport { \n extractVariable, \n extractStrVariable, \n extractLiteralValue, \n extractLiteral, \n extractTerm,\n isVariableInPattern \n} from './AlgebraUtils';\n\nconst dataFactory = new DataFactory();\n\n/**\n * Pushdown filters extracted from SPARQL FILTER clauses\n * Maps variable names to their filter conditions\n */\nexport interface PushdownFilters {\n [varName: string]: TermOperators;\n}\n\n/**\n * Result of extracting pushdown filters from an expression\n */\nexport interface PushdownResult {\n filters: PushdownFilters;\n remainder: Algebra.Expression | null;\n orBranches?: PushdownFilters[];\n orNonPushdownBranches?: Algebra.Expression[];\n}\n\n/**\n * FilterPushdownExtractor extracts pushdownable conditions from FILTER expressions\n */\nexport class FilterPushdownExtractor {\n /**\n * Extract pushdownable filters from an expression\n * Returns filters that can be pushed down and the remaining expression\n */\n extractPushdownFilters(\n expr: Algebra.Expression,\n pattern: Algebra.Pattern\n ): PushdownResult {\n if (expr.expressionType !== 'operator') {\n // Can't pushdown non-operator expressions\n return { filters: {}, remainder: expr };\n }\n\n const opExpr = expr as Algebra.OperatorExpression;\n const op = opExpr.operator.toLowerCase();\n\n // Handle AND: recursively extract from both sides\n if (op === '&&') {\n const filters: PushdownFilters = {};\n const remainders: Algebra.Expression[] = [];\n\n for (const arg of opExpr.args) {\n const result = this.extractPushdownFilters(arg, pattern);\n // Merge filters\n for (const [varName, ops] of Object.entries(result.filters)) {\n filters[varName] = { ...filters[varName], ...ops };\n }\n if (result.remainder) {\n remainders.push(result.remainder);\n }\n }\n\n // Combine remainders with AND\n let remainder: Algebra.Expression | null = null;\n if (remainders.length === 1) {\n remainder = remainders[0];\n } else if (remainders.length > 1) {\n remainder = {\n type: 'expression',\n expressionType: 'operator',\n operator: '&&',\n args: remainders,\n } as Algebra.OperatorExpression;\n }\n\n return { filters, remainder };\n }\n\n // Handle OR: try to convert to $in or OR branches\n if (op === '||') {\n return this.extractOrPushdown(opExpr, pattern);\n }\n\n // Try to extract single pushdown condition\n const result = this.tryExtractSinglePushdown(opExpr, pattern);\n if (result) {\n return { filters: result, remainder: null };\n }\n\n // Can't pushdown\n return { filters: {}, remainder: expr };\n }\n\n /**\n * Handle OR expression pushdown\n * - Same variable equality: convert to $in\n * - Different variables: create OR branches for separate queries\n * - Mixed (pushdownable + non-pushdownable): orBranches + orNonPushdownBranches\n */\n private extractOrPushdown(\n expr: Algebra.OperatorExpression,\n pattern: Algebra.Pattern\n ): PushdownResult {\n // Flatten OR tree and collect all branches\n const branches: Algebra.Expression[] = [];\n const collectBranches = (e: Algebra.Expression): void => {\n if (e.expressionType === 'operator') {\n const op = e as Algebra.OperatorExpression;\n if (op.operator.toLowerCase() === '||') {\n op.args.forEach(collectBranches);\n return;\n }\n }\n branches.push(e);\n };\n collectBranches(expr);\n\n // Try to extract pushdown from each branch\n const pushdownable: { filters: PushdownFilters; expr: Algebra.Expression }[] = [];\n const nonPushdownable: Algebra.Expression[] = [];\n\n for (const branch of branches) {\n if (branch.expressionType === 'operator') {\n const branchFilters = this.tryExtractSinglePushdown(branch as Algebra.OperatorExpression, pattern);\n if (branchFilters && Object.keys(branchFilters).length > 0) {\n pushdownable.push({ filters: branchFilters, expr: branch });\n continue;\n }\n }\n nonPushdownable.push(branch);\n }\n\n // If nothing pushdownable, return as remainder\n if (pushdownable.length === 0) {\n return { filters: {}, remainder: expr };\n }\n\n // Check if all are same-variable equality (can use $in)\n const inResult = this.tryConvertToIn(pushdownable);\n if (inResult && nonPushdownable.length === 0) {\n return { filters: inResult, remainder: null };\n }\n\n // Different variables or mixed: use OR branches\n const orBranches = pushdownable.map(p => p.filters);\n \n // Non-pushdownable branches need OR semantics (not AND), so use orNonPushdownBranches\n return { \n filters: {}, \n remainder: null, \n orBranches,\n orNonPushdownBranches: nonPushdownable.length > 0 ? nonPushdownable : undefined\n };\n }\n\n /**\n * Try to convert same-variable equalities to $in\n */\n private tryConvertToIn(\n branches: { filters: PushdownFilters; expr: Algebra.Expression }[]\n ): PushdownFilters | null {\n // Check if all branches are single-variable equality\n const varValues = new Map<string, string[]>();\n \n for (const { filters } of branches) {\n const keys = Object.keys(filters);\n if (keys.length !== 1) return null;\n \n const varName = keys[0];\n const ops = filters[varName];\n const opKeys = Object.keys(ops);\n \n if (opKeys.length !== 1 || !('$eq' in ops)) return null;\n \n if (!varValues.has(varName)) {\n varValues.set(varName, []);\n }\n varValues.get(varName)!.push(ops.$eq as string);\n }\n\n // Must be single variable\n if (varValues.size !== 1) return null;\n\n const [varName, values] = [...varValues.entries()][0];\n return { [varName]: { $in: values } };\n }\n\n /**\n * Try to extract pushdown filter from a single operator expression\n */\n tryExtractSinglePushdown(\n expr: Algebra.OperatorExpression,\n pattern: Algebra.Pattern\n ): PushdownFilters | null {\n const op = expr.operator.toLowerCase();\n const filters: PushdownFilters = {};\n\n // Comparison: =, !=, <, >, <=, >=\n if (['=', '!=', '<', '>', '<=', '>='].includes(op) && expr.args.length === 2) {\n const result = this.extractComparison(expr, op);\n if (result) {\n // Only pushdown if the variable is in the pattern\n if (isVariableInPattern(result.varName, pattern)) {\n filters[result.varName] = { [result.op]: result.value };\n return filters;\n }\n }\n }\n\n // STRSTARTS, STRENDS, CONTAINS\n // Database stores serialized literals like \"Alice\" (with quotes)\n // So we adjust the search pattern accordingly:\n // - STRSTARTS('A') -> $startsWith: '\"A' (add leading quote)\n // - STRENDS('e') -> $endsWith: 'e\"' (add trailing quote)\n // - CONTAINS('x') -> $contains: 'x' (quotes don't affect middle content)\n if (['strstarts', 'strends', 'contains'].includes(op) && expr.args.length === 2) {\n const varName = extractStrVariable(expr.args[0]);\n const value = extractLiteralValue(expr.args[1]);\n if (varName && value && isVariableInPattern(varName, pattern)) {\n if (op === 'strstarts') {\n filters[varName] = { $startsWith: '\"' + value };\n } else if (op === 'strends') {\n filters[varName] = { $endsWith: value + '\"' };\n } else {\n filters[varName] = { $contains: value };\n }\n return filters;\n }\n }\n\n // REGEX\n if (op === 'regex' && expr.args.length >= 2) {\n const varName = extractStrVariable(expr.args[0]);\n const regexPattern = extractLiteralValue(expr.args[1]);\n if (varName && regexPattern && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $regex: regexPattern };\n return filters;\n }\n }\n\n // IN\n if (op === 'in' && expr.args.length >= 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n const values: string[] = [];\n for (let i = 1; i < expr.args.length; i++) {\n const lit = extractLiteral(expr.args[i]);\n if (lit) {\n values.push(this.serializeExactValue(lit.value, lit.datatype));\n }\n }\n if (values.length > 0) {\n filters[varName] = { $in: values };\n return filters;\n }\n }\n }\n\n // NOT IN\n if (op === 'notin' && expr.args.length >= 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n const values: string[] = [];\n for (let i = 1; i < expr.args.length; i++) {\n const lit = extractLiteral(expr.args[i]);\n if (lit) {\n values.push(this.serializeExactValue(lit.value, lit.datatype));\n }\n }\n if (values.length > 0) {\n filters[varName] = { $notIn: values };\n return filters;\n }\n }\n }\n\n // BOUND\n if (op === 'bound' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $isNull: false };\n return filters;\n }\n }\n\n // !BOUND\n if (op === '!' && expr.args.length === 1) {\n const inner = expr.args[0];\n if (inner.expressionType === 'operator') {\n const innerOp = inner as Algebra.OperatorExpression;\n if (innerOp.operator.toLowerCase() === 'bound' && innerOp.args.length === 1) {\n const varName = extractVariable(innerOp.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $isNull: true };\n return filters;\n }\n }\n }\n }\n\n // Type checking functions\n // isiri/isuri: In Solid, IRIs are http:// or https://\n if ((op === 'isiri' || op === 'isuri') && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: 'http' };\n return filters;\n }\n }\n\n // isblank: Blank nodes start with _:\n if (op === 'isblank' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: '_:' };\n return filters;\n }\n }\n\n // isliteral: Literals start with \" (or N\\0 for numeric, D\\0 for datetime)\n if (op === 'isliteral' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n // Match strings starting with \", N (numeric), or D (datetime)\n filters[varName] = { $regex: '^[\"ND]' };\n return filters;\n }\n }\n\n // isnumeric: Our numeric literals start with N\\0\n if (op === 'isnumeric' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: 'N\\0' };\n return filters;\n }\n }\n\n // LANGMATCHES(LANG(?x), \"en\") - language tagged literals end with @lang\"\n // Serialization format: \"value\"@en\n if (op === 'langmatches' && expr.args.length === 2) {\n const langExpr = expr.args[0] as Algebra.Expression;\n const langPattern = expr.args[1];\n \n // First arg should be LANG(?var)\n if (langExpr.expressionType === 'operator' && \n (langExpr as Algebra.OperatorExpression).operator === 'lang' &&\n (langExpr as Algebra.OperatorExpression).args.length === 1) {\n const varName = extractVariable((langExpr as Algebra.OperatorExpression).args[0]);\n \n if (varName && isVariableInPattern(varName, pattern)) {\n // Get the language pattern\n if (langPattern.termType === 'Literal') {\n const lang = langPattern.value.toLowerCase();\n if (lang === '*') {\n // Match any language tagged literal - ends with @something\"\n // Use regex to match @[a-z]+\" pattern at end\n filters[varName] = { $regex: '@[a-zA-Z]+\"$' };\n } else {\n // Match specific language - ends with @lang\"\n filters[varName] = { $endsWith: `@${lang}\"` };\n }\n return filters;\n }\n }\n }\n }\n\n return null;\n }\n\n /**\n * Extract comparison operator info\n */\n private extractComparison(\n expr: Algebra.OperatorExpression,\n op: string\n ): { varName: string; op: string; value: string } | null {\n const [left, right] = expr.args;\n\n // ?var op literal\n const leftVar = extractVariable(left);\n const rightLiteral = extractLiteral(right);\n if (leftVar && rightLiteral) {\n const filterOp = this.mapComparisonOp(op);\n const value = this.serializeForComparison(rightLiteral.value, rightLiteral.datatype, filterOp);\n return { varName: leftVar, op: filterOp, value };\n }\n\n // literal op ?var (reverse)\n const leftLiteral = extractLiteral(left);\n const rightVar = extractVariable(right);\n if (leftLiteral && rightVar) {\n const reversedOp = this.reverseComparisonOp(op);\n const filterOp = this.mapComparisonOp(reversedOp);\n const value = this.serializeForComparison(leftLiteral.value, leftLiteral.datatype, filterOp);\n return { varName: rightVar, op: filterOp, value };\n }\n\n // ?var = <namedNode>\n const leftVarForTerm = extractVariable(left);\n const rightTerm = extractTerm(right);\n if (leftVarForTerm && rightTerm && (op === '=' || op === '!=')) {\n const filterOp = op === '=' ? '$eq' : '$ne';\n return { varName: leftVarForTerm, op: filterOp, value: serializeObject(rightTerm) };\n }\n\n return null;\n }\n\n private mapComparisonOp(op: string): string {\n const map: Record<string, string> = {\n '=': '$eq', '!=': '$ne', '<': '$lt', '>': '$gt', '<=': '$lte', '>=': '$gte'\n };\n return map[op] || '$eq';\n }\n\n private reverseComparisonOp(op: string): string {\n const map: Record<string, string> = {\n '<': '>', '>': '<', '<=': '>=', '>=': '<=', '=': '=', '!=': '!='\n };\n return map[op] || op;\n }\n\n /**\n * Serialize value for comparison\n * \n * Range comparison handling:\n * - $gt, $lte: Use fpstring + max suffix to be >= all values with same fpstring\n * - $lt, $gte: Use fpstring only (prefix), stored values are always > prefix\n * - $eq, $ne: Use exact serialization\n */\n serializeForComparison(value: string, datatype: string | undefined, filterOp: string): string {\n // Exact match uses full serialization\n if (filterOp === '$eq' || filterOp === '$ne') {\n return this.serializeExactValue(value, datatype);\n }\n \n // Range comparison\n if (datatype && NUMERIC_TYPES.has(datatype)) {\n const fpValue = `N${SEP}${fpEncode(value)}`;\n // $gt and $lte need max suffix to compare correctly\n if (filterOp === '$gt' || filterOp === '$lte') {\n return fpValue + SEP + '\\uffff';\n }\n // $lt and $gte use prefix only\n return fpValue;\n }\n \n if (datatype === DATETIME_TYPE) {\n const fpValue = `D${SEP}${fpEncode(new Date(value).valueOf())}`;\n if (filterOp === '$gt' || filterOp === '$lte') {\n return fpValue + SEP + '\\uffff';\n }\n return fpValue;\n }\n \n // For non-numeric types, use exact value (string comparison)\n return this.serializeExactValue(value, datatype);\n }\n\n serializeExactValue(value: string, datatype?: string): string {\n const lit = datatype \n ? dataFactory.literal(value, dataFactory.namedNode(datatype))\n : dataFactory.literal(value);\n return serializeObject(lit);\n }\n}\n"]}
1
+ {"version":3,"file":"FilterPushdownExtractor.js","sourceRoot":"","sources":["../../../src/storage/sparql/FilterPushdownExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * FilterPushdownExtractor - Extract pushdownable conditions from SPARQL FILTER\n * \n * Analyzes SPARQL FILTER expressions and extracts conditions that can be\n * pushed down to the database layer for efficient query execution.\n * \n * Supported pushdown operations:\n * - Comparison: =, !=, <, >, <=, >=\n * - String functions: STRSTARTS, STRENDS, CONTAINS, REGEX\n * - Type checking: isIRI, isBlank, isLiteral, isNumeric\n * - Language: LANGMATCHES\n * - Logical: AND, OR (with $in optimization)\n * - Set operations: IN, NOT IN\n * - Existence: BOUND, !BOUND\n */\n\nimport type { Algebra } from 'sparqlalgebrajs';\nimport { DataFactory } from 'rdf-data-factory';\n\nimport type { OperatorValue, TermOperators } from '../quint/types';\nimport { fpEncode, NUMERIC_TYPES, DATETIME_TYPE, SEP, serializeObject } from '../quint/serialization';\nimport { \n extractVariable, \n extractStrVariable, \n extractLiteralValue, \n extractLiteral, \n extractTerm,\n isVariableInPattern,\n getVariablePosition,\n} from './AlgebraUtils';\n\nconst dataFactory = new DataFactory();\n\n/**\n * Pushdown filters extracted from SPARQL FILTER clauses\n * Maps variable names to their filter conditions\n */\nexport interface PushdownFilters {\n [varName: string]: TermOperators;\n}\n\n/**\n * Result of extracting pushdown filters from an expression\n */\nexport interface PushdownResult {\n filters: PushdownFilters;\n remainder: Algebra.Expression | null;\n orBranches?: PushdownFilters[];\n orNonPushdownBranches?: Algebra.Expression[];\n}\n\n/**\n * FilterPushdownExtractor extracts pushdownable conditions from FILTER expressions\n */\nexport class FilterPushdownExtractor {\n /**\n * Extract pushdownable filters from an expression\n * Returns filters that can be pushed down and the remaining expression\n */\n extractPushdownFilters(\n expr: Algebra.Expression,\n pattern: Algebra.Pattern\n ): PushdownResult {\n if (expr.expressionType !== 'operator') {\n // Can't pushdown non-operator expressions\n return { filters: {}, remainder: expr };\n }\n\n const opExpr = expr as Algebra.OperatorExpression;\n const op = opExpr.operator.toLowerCase();\n\n // Handle AND: recursively extract from both sides\n if (op === '&&') {\n const filters: PushdownFilters = {};\n const remainders: Algebra.Expression[] = [];\n\n for (const arg of opExpr.args) {\n const result = this.extractPushdownFilters(arg, pattern);\n // Merge filters\n for (const [varName, ops] of Object.entries(result.filters)) {\n filters[varName] = { ...filters[varName], ...ops };\n }\n if (result.remainder) {\n remainders.push(result.remainder);\n }\n }\n\n // Combine remainders with AND\n let remainder: Algebra.Expression | null = null;\n if (remainders.length === 1) {\n remainder = remainders[0];\n } else if (remainders.length > 1) {\n remainder = {\n type: 'expression',\n expressionType: 'operator',\n operator: '&&',\n args: remainders,\n } as Algebra.OperatorExpression;\n }\n\n return { filters, remainder };\n }\n\n // Handle OR: try to convert to $in or OR branches\n if (op === '||') {\n return this.extractOrPushdown(opExpr, pattern);\n }\n\n // Try to extract single pushdown condition\n const result = this.tryExtractSinglePushdown(opExpr, pattern);\n if (result) {\n return { filters: result, remainder: null };\n }\n\n // Can't pushdown\n return { filters: {}, remainder: expr };\n }\n\n /**\n * Handle OR expression pushdown\n * - Same variable equality: convert to $in\n * - Different variables: create OR branches for separate queries\n * - Mixed (pushdownable + non-pushdownable): orBranches + orNonPushdownBranches\n */\n private extractOrPushdown(\n expr: Algebra.OperatorExpression,\n pattern: Algebra.Pattern\n ): PushdownResult {\n // Flatten OR tree and collect all branches\n const branches: Algebra.Expression[] = [];\n const collectBranches = (e: Algebra.Expression): void => {\n if (e.expressionType === 'operator') {\n const op = e as Algebra.OperatorExpression;\n if (op.operator.toLowerCase() === '||') {\n op.args.forEach(collectBranches);\n return;\n }\n }\n branches.push(e);\n };\n collectBranches(expr);\n\n // Try to extract pushdown from each branch\n const pushdownable: { filters: PushdownFilters; expr: Algebra.Expression }[] = [];\n const nonPushdownable: Algebra.Expression[] = [];\n\n for (const branch of branches) {\n if (branch.expressionType === 'operator') {\n const branchFilters = this.tryExtractSinglePushdown(branch as Algebra.OperatorExpression, pattern);\n if (branchFilters && Object.keys(branchFilters).length > 0) {\n pushdownable.push({ filters: branchFilters, expr: branch });\n continue;\n }\n }\n nonPushdownable.push(branch);\n }\n\n // If nothing pushdownable, return as remainder\n if (pushdownable.length === 0) {\n return { filters: {}, remainder: expr };\n }\n\n // Check if all are same-variable equality (can use $in)\n const inResult = this.tryConvertToIn(pushdownable);\n if (inResult && nonPushdownable.length === 0) {\n return { filters: inResult, remainder: null };\n }\n\n // Different variables or mixed: use OR branches\n const orBranches = pushdownable.map(p => p.filters);\n \n // Non-pushdownable branches need OR semantics (not AND), so use orNonPushdownBranches\n return { \n filters: {}, \n remainder: null, \n orBranches,\n orNonPushdownBranches: nonPushdownable.length > 0 ? nonPushdownable : undefined\n };\n }\n\n /**\n * Try to convert same-variable equalities to $in\n */\n private tryConvertToIn(\n branches: { filters: PushdownFilters; expr: Algebra.Expression }[]\n ): PushdownFilters | null {\n // Check if all branches are single-variable equality\n const varValues = new Map<string, string[]>();\n \n for (const { filters } of branches) {\n const keys = Object.keys(filters);\n if (keys.length !== 1) return null;\n \n const varName = keys[0];\n const ops = filters[varName];\n const opKeys = Object.keys(ops);\n \n if (opKeys.length !== 1 || !('$eq' in ops)) return null;\n \n if (!varValues.has(varName)) {\n varValues.set(varName, []);\n }\n varValues.get(varName)!.push(ops.$eq as string);\n }\n\n // Must be single variable\n if (varValues.size !== 1) return null;\n\n const [varName, values] = [...varValues.entries()][0];\n return { [varName]: { $in: values } };\n }\n\n /**\n * Try to extract pushdown filter from a single operator expression\n */\n tryExtractSinglePushdown(\n expr: Algebra.OperatorExpression,\n pattern: Algebra.Pattern\n ): PushdownFilters | null {\n const op = expr.operator.toLowerCase();\n const filters: PushdownFilters = {};\n\n // Comparison: =, !=, <, >, <=, >=\n if (['=', '!=', '<', '>', '<=', '>='].includes(op) && expr.args.length === 2) {\n const result = this.extractComparison(expr, op);\n if (result) {\n // Only pushdown if the variable is in the pattern\n if (isVariableInPattern(result.varName, pattern)) {\n filters[result.varName] = { [result.op]: result.value };\n return filters;\n }\n }\n }\n\n // STRSTARTS, STRENDS, CONTAINS\n // Subject/predicate/graph columns store raw IRI strings. Object string\n // operators are explicit lexical STR(?object) pushdowns so the store can\n // route text literals to object_text and IRIs/blank nodes to object_key.\n if (['strstarts', 'strends', 'contains'].includes(op) && expr.args.length === 2) {\n const varName = extractStrVariable(expr.args[0]);\n const value = extractLiteralValue(expr.args[1]);\n if (varName && value && isVariableInPattern(varName, pattern)) {\n const position = getVariablePosition(varName, pattern);\n const startsWithOp = position === 'object' ? '$strStartsWith' : '$startsWith';\n const endsWithOp = position === 'object' ? '$strEndsWith' : '$endsWith';\n const containsOp = position === 'object' ? '$strContains' : '$contains';\n if (op === 'strstarts') {\n filters[varName] = { [startsWithOp]: value };\n } else if (op === 'strends') {\n filters[varName] = { [endsWithOp]: value };\n } else {\n filters[varName] = { [containsOp]: value };\n }\n return filters;\n }\n }\n\n // REGEX\n if (op === 'regex' && expr.args.length >= 2) {\n const varName = extractStrVariable(expr.args[0]);\n const regexPattern = extractLiteralValue(expr.args[1]);\n if (varName && regexPattern && isVariableInPattern(varName, pattern)) {\n const position = getVariablePosition(varName, pattern);\n filters[varName] = position === 'object'\n ? { $strRegex: regexPattern }\n : { $regex: regexPattern };\n return filters;\n }\n }\n\n // IN\n if (op === 'in' && expr.args.length >= 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n const values: string[] = [];\n for (let i = 1; i < expr.args.length; i++) {\n const lit = extractLiteral(expr.args[i]);\n if (lit) {\n values.push(this.serializeExactValue(lit.value, lit.datatype));\n }\n }\n if (values.length > 0) {\n filters[varName] = { $in: values };\n return filters;\n }\n }\n }\n\n // NOT IN\n if (op === 'notin' && expr.args.length >= 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n const values: string[] = [];\n for (let i = 1; i < expr.args.length; i++) {\n const lit = extractLiteral(expr.args[i]);\n if (lit) {\n values.push(this.serializeExactValue(lit.value, lit.datatype));\n }\n }\n if (values.length > 0) {\n filters[varName] = { $notIn: values };\n return filters;\n }\n }\n }\n\n // BOUND\n if (op === 'bound' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $isNull: false };\n return filters;\n }\n }\n\n // !BOUND\n if (op === '!' && expr.args.length === 1) {\n const inner = expr.args[0];\n if (inner.expressionType === 'operator') {\n const innerOp = inner as Algebra.OperatorExpression;\n if (innerOp.operator.toLowerCase() === 'bound' && innerOp.args.length === 1) {\n const varName = extractVariable(innerOp.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $isNull: true };\n return filters;\n }\n }\n }\n }\n\n // Type checking functions\n // isiri/isuri: In Solid, IRIs are http:// or https://\n if ((op === 'isiri' || op === 'isuri') && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: 'http' };\n return filters;\n }\n }\n\n // isblank: Blank nodes start with _:\n if (op === 'isblank' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: '_:' };\n return filters;\n }\n }\n\n // isliteral: Literals start with \" (or N\\0 for numeric, D\\0 for datetime)\n if (op === 'isliteral' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n // Match strings starting with \", N (numeric), or D (datetime)\n filters[varName] = { $regex: '^[\"ND]' };\n return filters;\n }\n }\n\n // isnumeric: Our numeric literals start with N\\0\n if (op === 'isnumeric' && expr.args.length === 1) {\n const varName = extractVariable(expr.args[0]);\n if (varName && isVariableInPattern(varName, pattern)) {\n filters[varName] = { $startsWith: 'N\\0' };\n return filters;\n }\n }\n\n // LANGMATCHES(LANG(?x), \"en\") - language tagged literals end with @lang\"\n if (op === 'langmatches' && expr.args.length === 2) {\n const langExpr = expr.args[0] as Algebra.Expression;\n const langPattern = expr.args[1];\n \n // First arg should be LANG(?var)\n if (langExpr.expressionType === 'operator' && \n (langExpr as Algebra.OperatorExpression).operator === 'lang' &&\n (langExpr as Algebra.OperatorExpression).args.length === 1) {\n const varName = extractVariable((langExpr as Algebra.OperatorExpression).args[0]);\n \n if (varName && isVariableInPattern(varName, pattern)) {\n const position = getVariablePosition(varName, pattern);\n if (position === 'object') {\n if (langPattern.termType === 'Literal') {\n const lang = langPattern.value.toLowerCase();\n filters[varName] = { $language: lang };\n return filters;\n }\n }\n\n // Get the language pattern\n if (langPattern.termType === 'Literal') {\n const lang = langPattern.value.toLowerCase();\n if (lang === '*') {\n // Match any language tagged literal - ends with @something\"\n // Use regex to match @[a-z]+\" pattern at end\n filters[varName] = { $regex: '@[a-zA-Z]+\"$' };\n } else {\n // Match specific language - ends with @lang\"\n filters[varName] = { $endsWith: `@${lang}\"` };\n }\n return filters;\n }\n }\n }\n }\n\n return null;\n }\n\n /**\n * Extract comparison operator info\n */\n private extractComparison(\n expr: Algebra.OperatorExpression,\n op: string\n ): { varName: string; op: string; value: OperatorValue } | null {\n const [left, right] = expr.args;\n\n // ?var op literal\n const leftVar = extractVariable(left);\n const rightLiteral = extractLiteral(right);\n if (leftVar && rightLiteral) {\n const filterOp = this.mapComparisonOp(op);\n const value = this.serializeForComparison(rightLiteral.value, rightLiteral.datatype, filterOp);\n return { varName: leftVar, op: filterOp, value };\n }\n\n // literal op ?var (reverse)\n const leftLiteral = extractLiteral(left);\n const rightVar = extractVariable(right);\n if (leftLiteral && rightVar) {\n const reversedOp = this.reverseComparisonOp(op);\n const filterOp = this.mapComparisonOp(reversedOp);\n const value = this.serializeForComparison(leftLiteral.value, leftLiteral.datatype, filterOp);\n return { varName: rightVar, op: filterOp, value };\n }\n\n // ?var = <namedNode>\n const leftVarForTerm = extractVariable(left);\n const rightTerm = extractTerm(right);\n if (leftVarForTerm && rightTerm && (op === '=' || op === '!=')) {\n const filterOp = op === '=' ? '$eq' : '$ne';\n return { varName: leftVarForTerm, op: filterOp, value: rightTerm };\n }\n\n return null;\n }\n\n private mapComparisonOp(op: string): string {\n const map: Record<string, string> = {\n '=': '$eq', '!=': '$ne', '<': '$lt', '>': '$gt', '<=': '$lte', '>=': '$gte'\n };\n return map[op] || '$eq';\n }\n\n private reverseComparisonOp(op: string): string {\n const map: Record<string, string> = {\n '<': '>', '>': '<', '<=': '>=', '>=': '<=', '=': '=', '!=': '!='\n };\n return map[op] || op;\n }\n\n /**\n * Serialize value for comparison\n * \n * Range comparison handling:\n * - $gt, $lte: Use fpstring + max suffix to be >= all values with same fpstring\n * - $lt, $gte: Use fpstring only (prefix), stored values are always > prefix\n * - $eq, $ne: Use exact serialization\n */\n serializeForComparison(value: string, datatype: string | undefined, filterOp: string): string {\n // Exact match uses full serialization\n if (filterOp === '$eq' || filterOp === '$ne') {\n return this.serializeExactValue(value, datatype);\n }\n \n // Range comparison\n if (datatype && NUMERIC_TYPES.has(datatype)) {\n const fpValue = `N${SEP}${fpEncode(value)}`;\n // $gt and $lte need max suffix to compare correctly\n if (filterOp === '$gt' || filterOp === '$lte') {\n return fpValue + SEP + '\\uffff';\n }\n // $lt and $gte use prefix only\n return fpValue;\n }\n \n if (datatype === DATETIME_TYPE) {\n const fpValue = `D${SEP}${fpEncode(new Date(value).valueOf())}`;\n if (filterOp === '$gt' || filterOp === '$lte') {\n return fpValue + SEP + '\\uffff';\n }\n return fpValue;\n }\n \n // For non-numeric types, use exact value (string comparison)\n return this.serializeExactValue(value, datatype);\n }\n\n serializeExactValue(value: string, datatype?: string): string {\n const lit = datatype \n ? dataFactory.literal(value, dataFactory.namedNode(datatype))\n : dataFactory.literal(value);\n return serializeObject(lit);\n }\n}\n"]}
@@ -5,6 +5,21 @@ const node_url_1 = require("node:url");
5
5
  const global_logger_factory_1 = require("global-logger-factory");
6
6
  const community_server_1 = require("@solid/community-server");
7
7
  const PathUtil_1 = require("@solid/community-server/dist/util/PathUtil");
8
+ const SERVER_ROOT_SEGMENTS = new Set([
9
+ '.account',
10
+ '.oidc',
11
+ '.well-known',
12
+ 'admin',
13
+ 'api',
14
+ 'app',
15
+ 'auth',
16
+ 'dashboard',
17
+ 'login',
18
+ 'logout',
19
+ 'provision',
20
+ 'register',
21
+ 'settings',
22
+ ]);
8
23
  /**
9
24
  * Identifier strategy that accepts both the primary cluster host and any
10
25
  * node subdomain under the same base domain.
@@ -60,7 +75,20 @@ class ClusterIdentifierStrategy extends community_server_1.BaseIdentifierStrateg
60
75
  try {
61
76
  const target = new node_url_1.URL(identifier.path);
62
77
  if (target.hostname.toLowerCase() === this.baseHost) {
63
- return (0, PathUtil_1.ensureTrailingSlash)(target.href) === this.baseUrl;
78
+ if ((0, PathUtil_1.ensureTrailingSlash)(target.href) === this.baseUrl) {
79
+ return true;
80
+ }
81
+ const basePath = new node_url_1.URL(this.baseUrl).pathname;
82
+ if (!target.pathname.endsWith('/')) {
83
+ return false;
84
+ }
85
+ const pathname = (0, PathUtil_1.ensureTrailingSlash)(target.pathname);
86
+ if (!pathname.startsWith(basePath)) {
87
+ return false;
88
+ }
89
+ const relative = pathname.slice(basePath.length).replace(/^\/+/, '');
90
+ const segments = relative.split('/').filter(Boolean);
91
+ return segments.length === 1 && !SERVER_ROOT_SEGMENTS.has(segments[0].toLowerCase());
64
92
  }
65
93
  return target.pathname === '/' || target.pathname === '';
66
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ClusterIdentifierStrategy.js","sourceRoot":"","sources":["../../../src/util/identifiers/ClusterIdentifierStrategy.ts"],"names":[],"mappings":"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","sourcesContent":["import { URL } from 'node:url';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { BaseIdentifierStrategy } from '@solid/community-server';\nimport type { ResourceIdentifier } from '@solid/community-server';\nimport { ensureTrailingSlash } from '@solid/community-server/dist/util/PathUtil';\n\nexport interface ClusterIdentifierStrategyOptions {\n baseUrl: string;\n /**\n * 允许的额外 host 列表(用于 Docker/容器环境)\n * 可以是数组或逗号分隔的字符串,例如: ['cloud', 'idp'] 或 \"cloud,idp,localhost\"\n */\n allowedHosts?: string[] | string;\n}\n\n/**\n * Identifier strategy that accepts both the primary cluster host and any\n * node subdomain under the same base domain.\n */\nexport class ClusterIdentifierStrategy extends BaseIdentifierStrategy {\n private readonly logger = getLoggerFor(this);\n private readonly baseUrl: string;\n private readonly baseHost: string;\n private readonly allowedHosts: string[];\n\n public constructor(options: ClusterIdentifierStrategyOptions) {\n super();\n if (!options.baseUrl) {\n throw new Error('ClusterIdentifierStrategy requires a baseUrl.');\n }\n const parsed = new URL(options.baseUrl);\n this.baseHost = parsed.hostname.toLowerCase();\n this.baseUrl = ensureTrailingSlash(parsed.href);\n // 处理 allowedHosts:可以是数组或逗号分隔的字符串\n const hostsInput = options.allowedHosts ?? [];\n const hostsArray = Array.isArray(hostsInput)\n ? hostsInput\n : typeof hostsInput === 'string' && hostsInput.length > 0\n ? hostsInput.split(',').map(h => h.trim())\n : [];\n this.allowedHosts = hostsArray.map(h => h.toLowerCase());\n this.logger.info(`ClusterIdentifierStrategy initialized: baseUrl=${this.baseUrl}, baseHost=${this.baseHost}, allowedHosts=[${this.allowedHosts.join(', ')}]`);\n }\n\n public override supportsIdentifier(identifier: ResourceIdentifier): boolean {\n try {\n const target = new URL(identifier.path);\n const host = target.hostname.toLowerCase();\n\n // 检查是否在允许的 hosts 列表中\n if (this.allowedHosts.includes(host)) {\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> true (allowedHosts match: ${host})`);\n return true;\n }\n\n const hostMatches = host === this.baseHost || host.endsWith(`.${this.baseHost}`);\n\n if (!hostMatches) {\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> false (host mismatch: ${host} vs ${this.baseHost})`);\n return false;\n }\n\n if (host === this.baseHost) {\n const supported = identifier.path.startsWith(this.baseUrl);\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> ${supported} (baseUrl check)`);\n return supported;\n }\n\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> true (subdomain match)`);\n return true;\n } catch (error: unknown) {\n this.logger.warn(`Failed to parse identifier ${identifier.path}: ${(error as Error).message}`);\n return false;\n }\n }\n\n public override isRootContainer(identifier: ResourceIdentifier): boolean {\n try {\n const target = new URL(identifier.path);\n if (target.hostname.toLowerCase() === this.baseHost) {\n return ensureTrailingSlash(target.href) === this.baseUrl;\n }\n return target.pathname === '/' || target.pathname === '';\n } catch {\n return false;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ClusterIdentifierStrategy.js","sourceRoot":"","sources":["../../../src/util/identifiers/ClusterIdentifierStrategy.ts"],"names":[],"mappings":"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","sourcesContent":["import { URL } from 'node:url';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { BaseIdentifierStrategy } from '@solid/community-server';\nimport type { ResourceIdentifier } from '@solid/community-server';\nimport { ensureTrailingSlash } from '@solid/community-server/dist/util/PathUtil';\n\nexport interface ClusterIdentifierStrategyOptions {\n baseUrl: string;\n /**\n * 允许的额外 host 列表(用于 Docker/容器环境)\n * 可以是数组或逗号分隔的字符串,例如: ['cloud', 'idp'] 或 \"cloud,idp,localhost\"\n */\n allowedHosts?: string[] | string;\n}\n\nconst SERVER_ROOT_SEGMENTS = new Set([\n '.account',\n '.oidc',\n '.well-known',\n 'admin',\n 'api',\n 'app',\n 'auth',\n 'dashboard',\n 'login',\n 'logout',\n 'provision',\n 'register',\n 'settings',\n]);\n\n/**\n * Identifier strategy that accepts both the primary cluster host and any\n * node subdomain under the same base domain.\n */\nexport class ClusterIdentifierStrategy extends BaseIdentifierStrategy {\n private readonly logger = getLoggerFor(this);\n private readonly baseUrl: string;\n private readonly baseHost: string;\n private readonly allowedHosts: string[];\n\n public constructor(options: ClusterIdentifierStrategyOptions) {\n super();\n if (!options.baseUrl) {\n throw new Error('ClusterIdentifierStrategy requires a baseUrl.');\n }\n const parsed = new URL(options.baseUrl);\n this.baseHost = parsed.hostname.toLowerCase();\n this.baseUrl = ensureTrailingSlash(parsed.href);\n // 处理 allowedHosts:可以是数组或逗号分隔的字符串\n const hostsInput = options.allowedHosts ?? [];\n const hostsArray = Array.isArray(hostsInput)\n ? hostsInput\n : typeof hostsInput === 'string' && hostsInput.length > 0\n ? hostsInput.split(',').map(h => h.trim())\n : [];\n this.allowedHosts = hostsArray.map(h => h.toLowerCase());\n this.logger.info(`ClusterIdentifierStrategy initialized: baseUrl=${this.baseUrl}, baseHost=${this.baseHost}, allowedHosts=[${this.allowedHosts.join(', ')}]`);\n }\n\n public override supportsIdentifier(identifier: ResourceIdentifier): boolean {\n try {\n const target = new URL(identifier.path);\n const host = target.hostname.toLowerCase();\n\n // 检查是否在允许的 hosts 列表中\n if (this.allowedHosts.includes(host)) {\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> true (allowedHosts match: ${host})`);\n return true;\n }\n\n const hostMatches = host === this.baseHost || host.endsWith(`.${this.baseHost}`);\n\n if (!hostMatches) {\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> false (host mismatch: ${host} vs ${this.baseHost})`);\n return false;\n }\n\n if (host === this.baseHost) {\n const supported = identifier.path.startsWith(this.baseUrl);\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> ${supported} (baseUrl check)`);\n return supported;\n }\n\n this.logger.debug(`supportsIdentifier: ${identifier.path} -> true (subdomain match)`);\n return true;\n } catch (error: unknown) {\n this.logger.warn(`Failed to parse identifier ${identifier.path}: ${(error as Error).message}`);\n return false;\n }\n }\n\n public override isRootContainer(identifier: ResourceIdentifier): boolean {\n try {\n const target = new URL(identifier.path);\n if (target.hostname.toLowerCase() === this.baseHost) {\n if (ensureTrailingSlash(target.href) === this.baseUrl) {\n return true;\n }\n\n const basePath = new URL(this.baseUrl).pathname;\n if (!target.pathname.endsWith('/')) {\n return false;\n }\n\n const pathname = ensureTrailingSlash(target.pathname);\n if (!pathname.startsWith(basePath)) {\n return false;\n }\n\n const relative = pathname.slice(basePath.length).replace(/^\\/+/, '');\n const segments = relative.split('/').filter(Boolean);\n return segments.length === 1 && !SERVER_ROOT_SEGMENTS.has(segments[0].toLowerCase());\n }\n return target.pathname === '/' || target.pathname === '';\n } catch {\n return false;\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.2.43",
3
+ "version": "0.2.45",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",
@@ -8,26 +8,76 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
- const josePkgPath = path.join(__dirname, '..', 'node_modules', 'jose', 'package.json');
11
+ const nodeModulesPath = path.join(__dirname, '..', 'node_modules');
12
12
 
13
- if (!fs.existsSync(josePkgPath)) {
14
- console.log('[patch-jose] jose not found, skipping');
13
+ function findJosePackages(root) {
14
+ const results = [];
15
+ const queue = [root];
16
+ const ignored = new Set(['.cache', '.vite']);
17
+
18
+ while (queue.length > 0) {
19
+ const current = queue.shift();
20
+ let entries;
21
+ try {
22
+ entries = fs.readdirSync(current, { withFileTypes: true });
23
+ } catch {
24
+ continue;
25
+ }
26
+
27
+ for (const entry of entries) {
28
+ if (!entry.isDirectory() || ignored.has(entry.name)) {
29
+ continue;
30
+ }
31
+
32
+ const entryPath = path.join(current, entry.name);
33
+ if (entry.name === 'jose') {
34
+ const packagePath = path.join(entryPath, 'package.json');
35
+ if (fs.existsSync(packagePath)) {
36
+ results.push(packagePath);
37
+ }
38
+ continue;
39
+ }
40
+
41
+ queue.push(entryPath);
42
+ }
43
+ }
44
+
45
+ return results;
46
+ }
47
+
48
+ if (!fs.existsSync(nodeModulesPath)) {
49
+ console.log('[patch-jose] node_modules not found, skipping');
15
50
  process.exit(0);
16
51
  }
17
52
 
18
53
  try {
19
- let content = fs.readFileSync(josePkgPath, 'utf8');
20
- const original = content;
54
+ let patched = 0;
55
+ let alreadyPatched = 0;
56
+ let skipped = 0;
57
+
58
+ for (const josePkgPath of findJosePackages(nodeModulesPath)) {
59
+ let content = fs.readFileSync(josePkgPath, 'utf8');
60
+ const original = content;
61
+ const packageRoot = path.dirname(josePkgPath);
62
+ const nodeEsmEntry = path.join(packageRoot, 'dist', 'node', 'esm', 'index.js');
21
63
 
22
- // Replace all bun exports from browser to node/esm
23
- content = content.replace(/"bun": "\.\/dist\/browser\//g, '"bun": "./dist/node/esm/');
64
+ if (!fs.existsSync(nodeEsmEntry)) {
65
+ skipped++;
66
+ continue;
67
+ }
24
68
 
25
- if (content !== original) {
26
- fs.writeFileSync(josePkgPath, content);
27
- console.log('[patch-jose] Patched jose exports for Bun compatibility');
28
- } else {
29
- console.log('[patch-jose] jose already patched');
69
+ // Replace all bun exports from browser to node/esm.
70
+ content = content.replace(/"bun": "\.\/dist\/browser\//g, '"bun": "./dist/node/esm/');
71
+
72
+ if (content !== original) {
73
+ fs.writeFileSync(josePkgPath, content);
74
+ patched++;
75
+ } else {
76
+ alreadyPatched++;
77
+ }
30
78
  }
79
+
80
+ console.log(`[patch-jose] Patched ${patched} jose package(s); ${alreadyPatched} already patched; ${skipped} skipped`);
31
81
  } catch (err) {
32
82
  console.error('[patch-jose] Failed to patch jose:', err.message);
33
83
  process.exit(1);