@tanstack/powersync-db-collection 0.1.42 → 0.1.44

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.
@@ -31,7 +31,10 @@ function compileExpression(exp, params, compileOptions) {
31
31
  );
32
32
  }
33
33
  const columnName = exp.path[0];
34
- if (compileOptions?.jsonColumn && columnName !== `id`) {
34
+ if (compileOptions?.jsonColumn && columnName === `id`) {
35
+ const prefix = compileOptions.jsonColumn.split(".")[0];
36
+ return `${prefix}.${quoteIdentifier(columnName)}`;
37
+ } else if (compileOptions?.jsonColumn) {
35
38
  return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`;
36
39
  }
37
40
  return quoteIdentifier(columnName);
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-compiler.cjs","sources":["../../src/sqlite-compiler.ts"],"sourcesContent":["import type { IR, LoadSubsetOptions } from '@tanstack/db'\n\n/**\n * Result of compiling LoadSubsetOptions to SQLite\n */\nexport interface SQLiteCompiledQuery {\n /** The WHERE clause (without \"WHERE\" keyword), e.g., \"price > ?\" */\n where?: string\n /** The ORDER BY clause (without \"ORDER BY\" keyword), e.g., \"price DESC\" */\n orderBy?: string\n /** The LIMIT value */\n limit?: number\n /** Parameter values in order, to be passed to SQLite query */\n params: Array<unknown>\n}\n\n/**\n * Options for controlling how SQL is compiled.\n */\nexport interface CompileSQLiteOptions {\n /**\n * When set, column references emit `json_extract(<jsonColumn>, '$.<columnName>')`\n * instead of `\"<columnName>\"`. The `id` column is excluded since it's stored\n * as a direct column in the tracked table.\n */\n jsonColumn?: string\n}\n\n/**\n * Compiles TanStack DB LoadSubsetOptions to SQLite query components.\n *\n * @example\n * ```typescript\n * const compiled = compileSQLite({\n * where: { type: 'func', name: 'gt', args: [\n * { type: 'ref', path: ['price'] },\n * { type: 'val', value: 100 }\n * ]},\n * orderBy: [{ expression: { type: 'ref', path: ['price'] }, compareOptions: { direction: 'desc', nulls: 'last' } }],\n * limit: 50\n * })\n * // Result: { where: '\"price\" > ?', orderBy: '\"price\" DESC', limit: 50, params: [100] }\n * ```\n */\nexport function compileSQLite(\n options: LoadSubsetOptions,\n compileOptions?: CompileSQLiteOptions,\n): SQLiteCompiledQuery {\n const { where, orderBy, limit } = options\n\n const params: Array<unknown> = []\n const result: SQLiteCompiledQuery = { params }\n\n if (where) {\n result.where = compileExpression(where, params, compileOptions)\n }\n\n if (orderBy) {\n result.orderBy = compileOrderBy(orderBy, params, compileOptions)\n }\n\n if (limit !== undefined) {\n result.limit = limit\n }\n\n return result\n}\n\n/**\n * Quote SQLite identifiers to handle column names correctly.\n * SQLite uses double quotes for identifiers.\n */\nfunction quoteIdentifier(name: string): string {\n // Escape any double quotes in the name by doubling them\n const escaped = name.replace(/\"/g, `\"\"`)\n return `\"${escaped}\"`\n}\n\n/**\n * Compiles a BasicExpression to a SQL string, mutating the params array.\n */\nfunction compileExpression(\n exp: IR.BasicExpression<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n switch (exp.type) {\n case `val`:\n params.push(exp.value)\n return `?`\n case `ref`: {\n if (exp.path.length !== 1) {\n throw new Error(\n `SQLite compiler doesn't support nested properties: ${exp.path.join(`.`)}`,\n )\n }\n const columnName = exp.path[0]!\n if (compileOptions?.jsonColumn && columnName !== `id`) {\n return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`\n }\n return quoteIdentifier(columnName)\n }\n case `func`:\n return compileFunction(exp, params, compileOptions)\n default:\n throw new Error(`Unknown expression type: ${(exp as any).type}`)\n }\n}\n\n/**\n * Compiles an OrderBy array to a SQL ORDER BY clause.\n */\nfunction compileOrderBy(\n orderBy: IR.OrderBy,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const clauses = orderBy.map((clause: IR.OrderByClause) =>\n compileOrderByClause(clause, params, compileOptions),\n )\n return clauses.join(`, `)\n}\n\n/**\n * Compiles a single OrderByClause to SQL.\n */\nfunction compileOrderByClause(\n clause: IR.OrderByClause,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { expression, compareOptions } = clause\n let sql = compileExpression(expression, params, compileOptions)\n\n if (compareOptions.direction === `desc`) {\n sql = `${sql} DESC`\n }\n\n // SQLite supports NULLS FIRST/LAST (since 3.30.0)\n if (compareOptions.nulls === `first`) {\n sql = `${sql} NULLS FIRST`\n } else {\n // Default to NULLS LAST (nulls === 'last')\n sql = `${sql} NULLS LAST`\n }\n\n return sql\n}\n\n/**\n * Check if a BasicExpression represents a null/undefined value\n */\nfunction isNullValue(exp: IR.BasicExpression<unknown>): boolean {\n return exp.type === `val` && (exp.value === null || exp.value === undefined)\n}\n\n/**\n * Compiles a function expression (operator) to SQL.\n */\nfunction compileFunction(\n exp: IR.Func<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { name, args } = exp\n\n // Check for null values in comparison operators\n if (isComparisonOp(name)) {\n const hasNullArg = args.some((arg: IR.BasicExpression) => isNullValue(arg))\n if (hasNullArg) {\n throw new Error(\n `Cannot use null/undefined with '${name}' operator. ` +\n `Use isNull() to check for null values.`,\n )\n }\n }\n\n // Compile arguments\n const compiledArgs = args.map((arg: IR.BasicExpression) =>\n compileExpression(arg, params, compileOptions),\n )\n\n // Handle different operator types\n switch (name) {\n // Binary comparison operators\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`${name} expects 2 arguments`)\n }\n const opSymbol = getComparisonOp(name)\n return `${compiledArgs[0]} ${opSymbol} ${compiledArgs[1]}`\n }\n\n // Logical operators\n case `and`:\n case `or`: {\n if (compiledArgs.length < 2) {\n throw new Error(`${name} expects at least 2 arguments`)\n }\n const opKeyword = name === `and` ? `AND` : `OR`\n return compiledArgs\n .map((arg: string) => `(${arg})`)\n .join(` ${opKeyword} `)\n }\n\n case `not`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`not expects 1 argument`)\n }\n // Check if argument is isNull/isUndefined for IS NOT NULL\n const arg = args[0]\n if (arg && arg.type === `func`) {\n if (arg.name === `isNull` || arg.name === `isUndefined`) {\n const innerArg = compileExpression(\n arg.args[0]!,\n params,\n compileOptions,\n )\n return `${innerArg} IS NOT NULL`\n }\n }\n return `NOT (${compiledArgs[0]})`\n }\n\n // Null checking\n case `isNull`:\n case `isUndefined`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`${name} expects 1 argument`)\n }\n return `${compiledArgs[0]} IS NULL`\n }\n\n // IN operator\n case `in`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`in expects 2 arguments (column and array)`)\n }\n // The second argument should be an array value\n // We need to handle this specially - expand the array into multiple placeholders\n const lastParamIndex = params.length - 1\n const arrayValue = params[lastParamIndex]\n\n if (!Array.isArray(arrayValue)) {\n throw new Error(`in operator requires an array value`)\n }\n\n // Remove the array param and add individual values\n params.pop()\n const placeholders = arrayValue.map(() => {\n params.push(arrayValue[params.length - lastParamIndex])\n return `?`\n })\n\n // Re-add individual values properly\n params.length = lastParamIndex // Reset to before array\n for (const val of arrayValue) {\n params.push(val)\n }\n\n return `${compiledArgs[0]} IN (${placeholders.join(`, `)})`\n }\n\n // String operators\n case `like`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`like expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]}`\n }\n\n case `ilike`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`ilike expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]} COLLATE NOCASE`\n }\n\n // String case functions\n case `upper`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`upper expects 1 argument`)\n }\n return `UPPER(${compiledArgs[0]})`\n }\n\n case `lower`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`lower expects 1 argument`)\n }\n return `LOWER(${compiledArgs[0]})`\n }\n\n case `length`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`length expects 1 argument`)\n }\n return `LENGTH(${compiledArgs[0]})`\n }\n\n case `concat`: {\n if (compiledArgs.length < 1) {\n throw new Error(`concat expects at least 1 argument`)\n }\n return `CONCAT(${compiledArgs.join(`, `)})`\n }\n\n case `add`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`add expects 2 arguments`)\n }\n return `${compiledArgs[0]} + ${compiledArgs[1]}`\n }\n\n // Null fallback\n case `coalesce`: {\n if (compiledArgs.length < 1) {\n throw new Error(`coalesce expects at least 1 argument`)\n }\n return `COALESCE(${compiledArgs.join(`, `)})`\n }\n\n default:\n throw new Error(\n `Operator '${name}' is not supported in PowerSync on-demand sync. ` +\n `Supported operators: eq, gt, gte, lt, lte, and, or, not, isNull, in, like, ilike, upper, lower, length, concat, add, coalesce`,\n )\n }\n}\n\n/**\n * Check if operator is a comparison operator\n */\nfunction isComparisonOp(name: string): boolean {\n return [`eq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`].includes(name)\n}\n\n/**\n * Get the SQL symbol for a comparison operator\n */\nfunction getComparisonOp(name: string): string {\n const ops: Record<string, string> = {\n eq: `=`,\n gt: `>`,\n gte: `>=`,\n lt: `<`,\n lte: `<=`,\n }\n return ops[name]!\n}\n"],"names":[],"mappings":";;AA4CO,SAAS,cACd,SACA,gBACqB;AACrB,QAAM,EAAE,OAAO,SAAS,MAAA,IAAU;AAElC,QAAM,SAAyB,CAAA;AAC/B,QAAM,SAA8B,EAAE,OAAA;AAEtC,MAAI,OAAO;AACT,WAAO,QAAQ,kBAAkB,OAAO,QAAQ,cAAc;AAAA,EAChE;AAEA,MAAI,SAAS;AACX,WAAO,UAAU,eAAe,SAAS,QAAQ,cAAc;AAAA,EACjE;AAEA,MAAI,UAAU,QAAW;AACvB,WAAO,QAAQ;AAAA,EACjB;AAEA,SAAO;AACT;AAMA,SAAS,gBAAgB,MAAsB;AAE7C,QAAM,UAAU,KAAK,QAAQ,MAAM,IAAI;AACvC,SAAO,IAAI,OAAO;AACpB;AAKA,SAAS,kBACP,KACA,QACA,gBACQ;AACR,UAAQ,IAAI,MAAA;AAAA,IACV,KAAK;AACH,aAAO,KAAK,IAAI,KAAK;AACrB,aAAO;AAAA,IACT,KAAK,OAAO;AACV,UAAI,IAAI,KAAK,WAAW,GAAG;AACzB,cAAM,IAAI;AAAA,UACR,sDAAsD,IAAI,KAAK,KAAK,GAAG,CAAC;AAAA,QAAA;AAAA,MAE5E;AACA,YAAM,aAAa,IAAI,KAAK,CAAC;AAC7B,UAAI,gBAAgB,cAAc,eAAe,MAAM;AACrD,eAAO,gBAAgB,eAAe,UAAU,QAAQ,UAAU;AAAA,MACpE;AACA,aAAO,gBAAgB,UAAU;AAAA,IACnC;AAAA,IACA,KAAK;AACH,aAAO,gBAAgB,KAAK,QAAQ,cAAc;AAAA,IACpD;AACE,YAAM,IAAI,MAAM,4BAA6B,IAAY,IAAI,EAAE;AAAA,EAAA;AAErE;AAKA,SAAS,eACP,SACA,QACA,gBACQ;AACR,QAAM,UAAU,QAAQ;AAAA,IAAI,CAAC,WAC3B,qBAAqB,QAAQ,QAAQ,cAAc;AAAA,EAAA;AAErD,SAAO,QAAQ,KAAK,IAAI;AAC1B;AAKA,SAAS,qBACP,QACA,QACA,gBACQ;AACR,QAAM,EAAE,YAAY,eAAA,IAAmB;AACvC,MAAI,MAAM,kBAAkB,YAAY,QAAQ,cAAc;AAE9D,MAAI,eAAe,cAAc,QAAQ;AACvC,UAAM,GAAG,GAAG;AAAA,EACd;AAGA,MAAI,eAAe,UAAU,SAAS;AACpC,UAAM,GAAG,GAAG;AAAA,EACd,OAAO;AAEL,UAAM,GAAG,GAAG;AAAA,EACd;AAEA,SAAO;AACT;AAKA,SAAS,YAAY,KAA2C;AAC9D,SAAO,IAAI,SAAS,UAAU,IAAI,UAAU,QAAQ,IAAI,UAAU;AACpE;AAKA,SAAS,gBACP,KACA,QACA,gBACQ;AACR,QAAM,EAAE,MAAM,KAAA,IAAS;AAGvB,MAAI,eAAe,IAAI,GAAG;AACxB,UAAM,aAAa,KAAK,KAAK,CAAC,QAA4B,YAAY,GAAG,CAAC;AAC1E,QAAI,YAAY;AACd,YAAM,IAAI;AAAA,QACR,mCAAmC,IAAI;AAAA,MAAA;AAAA,IAG3C;AAAA,EACF;AAGA,QAAM,eAAe,KAAK;AAAA,IAAI,CAAC,QAC7B,kBAAkB,KAAK,QAAQ,cAAc;AAAA,EAAA;AAI/C,UAAQ,MAAA;AAAA;AAAA,IAEN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,OAAO;AACV,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,GAAG,IAAI,sBAAsB;AAAA,MAC/C;AACA,YAAM,WAAW,gBAAgB,IAAI;AACrC,aAAO,GAAG,aAAa,CAAC,CAAC,IAAI,QAAQ,IAAI,aAAa,CAAC,CAAC;AAAA,IAC1D;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK,MAAM;AACT,UAAI,aAAa,SAAS,GAAG;AAC3B,cAAM,IAAI,MAAM,GAAG,IAAI,+BAA+B;AAAA,MACxD;AACA,YAAM,YAAY,SAAS,QAAQ,QAAQ;AAC3C,aAAO,aACJ,IAAI,CAAC,QAAgB,IAAI,GAAG,GAAG,EAC/B,KAAK,IAAI,SAAS,GAAG;AAAA,IAC1B;AAAA,IAEA,KAAK,OAAO;AACV,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,wBAAwB;AAAA,MAC1C;AAEA,YAAM,MAAM,KAAK,CAAC;AAClB,UAAI,OAAO,IAAI,SAAS,QAAQ;AAC9B,YAAI,IAAI,SAAS,YAAY,IAAI,SAAS,eAAe;AACvD,gBAAM,WAAW;AAAA,YACf,IAAI,KAAK,CAAC;AAAA,YACV;AAAA,YACA;AAAA,UAAA;AAEF,iBAAO,GAAG,QAAQ;AAAA,QACpB;AAAA,MACF;AACA,aAAO,QAAQ,aAAa,CAAC,CAAC;AAAA,IAChC;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK,eAAe;AAClB,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,GAAG,IAAI,qBAAqB;AAAA,MAC9C;AACA,aAAO,GAAG,aAAa,CAAC,CAAC;AAAA,IAC3B;AAAA;AAAA,IAGA,KAAK,MAAM;AACT,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AAGA,YAAM,iBAAiB,OAAO,SAAS;AACvC,YAAM,aAAa,OAAO,cAAc;AAExC,UAAI,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9B,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AAGA,aAAO,IAAA;AACP,YAAM,eAAe,WAAW,IAAI,MAAM;AACxC,eAAO,KAAK,WAAW,OAAO,SAAS,cAAc,CAAC;AACtD,eAAO;AAAA,MACT,CAAC;AAGD,aAAO,SAAS;AAChB,iBAAW,OAAO,YAAY;AAC5B,eAAO,KAAK,GAAG;AAAA,MACjB;AAEA,aAAO,GAAG,aAAa,CAAC,CAAC,QAAQ,aAAa,KAAK,IAAI,CAAC;AAAA,IAC1D;AAAA;AAAA,IAGA,KAAK,QAAQ;AACX,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,GAAG,aAAa,CAAC,CAAC,SAAS,aAAa,CAAC,CAAC;AAAA,IACnD;AAAA,IAEA,KAAK,SAAS;AACZ,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AACA,aAAO,GAAG,aAAa,CAAC,CAAC,SAAS,aAAa,CAAC,CAAC;AAAA,IACnD;AAAA;AAAA,IAGA,KAAK,SAAS;AACZ,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,SAAS,aAAa,CAAC,CAAC;AAAA,IACjC;AAAA,IAEA,KAAK,SAAS;AACZ,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,SAAS,aAAa,CAAC,CAAC;AAAA,IACjC;AAAA,IAEA,KAAK,UAAU;AACb,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AACA,aAAO,UAAU,aAAa,CAAC,CAAC;AAAA,IAClC;AAAA,IAEA,KAAK,UAAU;AACb,UAAI,aAAa,SAAS,GAAG;AAC3B,cAAM,IAAI,MAAM,oCAAoC;AAAA,MACtD;AACA,aAAO,UAAU,aAAa,KAAK,IAAI,CAAC;AAAA,IAC1C;AAAA,IAEA,KAAK,OAAO;AACV,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,yBAAyB;AAAA,MAC3C;AACA,aAAO,GAAG,aAAa,CAAC,CAAC,MAAM,aAAa,CAAC,CAAC;AAAA,IAChD;AAAA;AAAA,IAGA,KAAK,YAAY;AACf,UAAI,aAAa,SAAS,GAAG;AAC3B,cAAM,IAAI,MAAM,sCAAsC;AAAA,MACxD;AACA,aAAO,YAAY,aAAa,KAAK,IAAI,CAAC;AAAA,IAC5C;AAAA,IAEA;AACE,YAAM,IAAI;AAAA,QACR,aAAa,IAAI;AAAA,MAAA;AAAA,EAEnB;AAEN;AAKA,SAAS,eAAe,MAAuB;AAC7C,SAAO,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,QAAQ,OAAO,EAAE,SAAS,IAAI;AACxE;AAKA,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,MAA8B;AAAA,IAClC,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,KAAK;AAAA,IACL,IAAI;AAAA,IACJ,KAAK;AAAA,EAAA;AAEP,SAAO,IAAI,IAAI;AACjB;;"}
1
+ {"version":3,"file":"sqlite-compiler.cjs","sources":["../../src/sqlite-compiler.ts"],"sourcesContent":["import type { IR, LoadSubsetOptions } from '@tanstack/db'\n\n/**\n * Result of compiling LoadSubsetOptions to SQLite\n */\nexport interface SQLiteCompiledQuery {\n /** The WHERE clause (without \"WHERE\" keyword), e.g., \"price > ?\" */\n where?: string\n /** The ORDER BY clause (without \"ORDER BY\" keyword), e.g., \"price DESC\" */\n orderBy?: string\n /** The LIMIT value */\n limit?: number\n /** Parameter values in order, to be passed to SQLite query */\n params: Array<unknown>\n}\n\n/**\n * Options for controlling how SQL is compiled.\n */\nexport interface CompileSQLiteOptions {\n /**\n * When set, column references emit `json_extract(<jsonColumn>, '$.<columnName>')`\n * instead of `\"<columnName>\"`. The `id` column is excluded since it's stored\n * as a direct column in the tracked table.\n */\n jsonColumn?: string\n}\n\n/**\n * Compiles TanStack DB LoadSubsetOptions to SQLite query components.\n *\n * @example\n * ```typescript\n * const compiled = compileSQLite({\n * where: { type: 'func', name: 'gt', args: [\n * { type: 'ref', path: ['price'] },\n * { type: 'val', value: 100 }\n * ]},\n * orderBy: [{ expression: { type: 'ref', path: ['price'] }, compareOptions: { direction: 'desc', nulls: 'last' } }],\n * limit: 50\n * })\n * // Result: { where: '\"price\" > ?', orderBy: '\"price\" DESC', limit: 50, params: [100] }\n * ```\n */\nexport function compileSQLite(\n options: LoadSubsetOptions,\n compileOptions?: CompileSQLiteOptions,\n): SQLiteCompiledQuery {\n const { where, orderBy, limit } = options\n\n const params: Array<unknown> = []\n const result: SQLiteCompiledQuery = { params }\n\n if (where) {\n result.where = compileExpression(where, params, compileOptions)\n }\n\n if (orderBy) {\n result.orderBy = compileOrderBy(orderBy, params, compileOptions)\n }\n\n if (limit !== undefined) {\n result.limit = limit\n }\n\n return result\n}\n\n/**\n * Quote SQLite identifiers to handle column names correctly.\n * SQLite uses double quotes for identifiers.\n */\nfunction quoteIdentifier(name: string): string {\n // Escape any double quotes in the name by doubling them\n const escaped = name.replace(/\"/g, `\"\"`)\n return `\"${escaped}\"`\n}\n\n/**\n * Compiles a BasicExpression to a SQL string, mutating the params array.\n */\nfunction compileExpression(\n exp: IR.BasicExpression<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n switch (exp.type) {\n case `val`:\n params.push(exp.value)\n return `?`\n case `ref`: {\n if (exp.path.length !== 1) {\n throw new Error(\n `SQLite compiler doesn't support nested properties: ${exp.path.join(`.`)}`,\n )\n }\n const columnName = exp.path[0]!\n\n // PowerSync stores `id` as a top-level row column rather than inside the\n // JSON `data` object, so we skip json_extract. However, when compiling for\n // trigger WHEN clauses we still need the OLD./NEW. prefix. Extract it from\n // the jsonColumn option.\n if (compileOptions?.jsonColumn && columnName === `id`) {\n const prefix = compileOptions.jsonColumn.split('.')[0]!\n return `${prefix}.${quoteIdentifier(columnName)}`\n } else if (compileOptions?.jsonColumn) {\n return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`\n }\n return quoteIdentifier(columnName)\n }\n case `func`:\n return compileFunction(exp, params, compileOptions)\n default:\n throw new Error(`Unknown expression type: ${(exp as any).type}`)\n }\n}\n\n/**\n * Compiles an OrderBy array to a SQL ORDER BY clause.\n */\nfunction compileOrderBy(\n orderBy: IR.OrderBy,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const clauses = orderBy.map((clause: IR.OrderByClause) =>\n compileOrderByClause(clause, params, compileOptions),\n )\n return clauses.join(`, `)\n}\n\n/**\n * Compiles a single OrderByClause to SQL.\n */\nfunction compileOrderByClause(\n clause: IR.OrderByClause,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { expression, compareOptions } = clause\n let sql = compileExpression(expression, params, compileOptions)\n\n if (compareOptions.direction === `desc`) {\n sql = `${sql} DESC`\n }\n\n // SQLite supports NULLS FIRST/LAST (since 3.30.0)\n if (compareOptions.nulls === `first`) {\n sql = `${sql} NULLS FIRST`\n } else {\n // Default to NULLS LAST (nulls === 'last')\n sql = `${sql} NULLS LAST`\n }\n\n return sql\n}\n\n/**\n * Check if a BasicExpression represents a null/undefined value\n */\nfunction isNullValue(exp: IR.BasicExpression<unknown>): boolean {\n return exp.type === `val` && (exp.value === null || exp.value === undefined)\n}\n\n/**\n * Compiles a function expression (operator) to SQL.\n */\nfunction compileFunction(\n exp: IR.Func<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { name, args } = exp\n\n // Check for null values in comparison operators\n if (isComparisonOp(name)) {\n const hasNullArg = args.some((arg: IR.BasicExpression) => isNullValue(arg))\n if (hasNullArg) {\n throw new Error(\n `Cannot use null/undefined with '${name}' operator. ` +\n `Use isNull() to check for null values.`,\n )\n }\n }\n\n // Compile arguments\n const compiledArgs = args.map((arg: IR.BasicExpression) =>\n compileExpression(arg, params, compileOptions),\n )\n\n // Handle different operator types\n switch (name) {\n // Binary comparison operators\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`${name} expects 2 arguments`)\n }\n const opSymbol = getComparisonOp(name)\n return `${compiledArgs[0]} ${opSymbol} ${compiledArgs[1]}`\n }\n\n // Logical operators\n case `and`:\n case `or`: {\n if (compiledArgs.length < 2) {\n throw new Error(`${name} expects at least 2 arguments`)\n }\n const opKeyword = name === `and` ? `AND` : `OR`\n return compiledArgs\n .map((arg: string) => `(${arg})`)\n .join(` ${opKeyword} `)\n }\n\n case `not`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`not expects 1 argument`)\n }\n // Check if argument is isNull/isUndefined for IS NOT NULL\n const arg = args[0]\n if (arg && arg.type === `func`) {\n if (arg.name === `isNull` || arg.name === `isUndefined`) {\n const innerArg = compileExpression(\n arg.args[0]!,\n params,\n compileOptions,\n )\n return `${innerArg} IS NOT NULL`\n }\n }\n return `NOT (${compiledArgs[0]})`\n }\n\n // Null checking\n case `isNull`:\n case `isUndefined`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`${name} expects 1 argument`)\n }\n return `${compiledArgs[0]} IS NULL`\n }\n\n // IN operator\n case `in`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`in expects 2 arguments (column and array)`)\n }\n // The second argument should be an array value\n // We need to handle this specially - expand the array into multiple placeholders\n const lastParamIndex = params.length - 1\n const arrayValue = params[lastParamIndex]\n\n if (!Array.isArray(arrayValue)) {\n throw new Error(`in operator requires an array value`)\n }\n\n // Remove the array param and add individual values\n params.pop()\n const placeholders = arrayValue.map(() => {\n params.push(arrayValue[params.length - lastParamIndex])\n return `?`\n })\n\n // Re-add individual values properly\n params.length = lastParamIndex // Reset to before array\n for (const val of arrayValue) {\n params.push(val)\n }\n\n return `${compiledArgs[0]} IN (${placeholders.join(`, `)})`\n }\n\n // String operators\n case `like`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`like expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]}`\n }\n\n case `ilike`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`ilike expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]} COLLATE NOCASE`\n }\n\n // String case functions\n case `upper`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`upper expects 1 argument`)\n }\n return `UPPER(${compiledArgs[0]})`\n }\n\n case `lower`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`lower expects 1 argument`)\n }\n return `LOWER(${compiledArgs[0]})`\n }\n\n case `length`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`length expects 1 argument`)\n }\n return `LENGTH(${compiledArgs[0]})`\n }\n\n case `concat`: {\n if (compiledArgs.length < 1) {\n throw new Error(`concat expects at least 1 argument`)\n }\n return `CONCAT(${compiledArgs.join(`, `)})`\n }\n\n case `add`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`add expects 2 arguments`)\n }\n return `${compiledArgs[0]} + ${compiledArgs[1]}`\n }\n\n // Null fallback\n case `coalesce`: {\n if (compiledArgs.length < 1) {\n throw new Error(`coalesce expects at least 1 argument`)\n }\n return `COALESCE(${compiledArgs.join(`, `)})`\n }\n\n default:\n throw new Error(\n `Operator '${name}' is not supported in PowerSync on-demand sync. ` +\n `Supported operators: eq, gt, gte, lt, lte, and, or, not, isNull, in, like, ilike, upper, lower, length, concat, add, coalesce`,\n )\n }\n}\n\n/**\n * Check if operator is a comparison operator\n */\nfunction isComparisonOp(name: string): boolean {\n return [`eq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`].includes(name)\n}\n\n/**\n * Get the SQL symbol for a comparison operator\n */\nfunction getComparisonOp(name: string): string {\n const ops: Record<string, string> = {\n eq: `=`,\n gt: `>`,\n gte: `>=`,\n lt: `<`,\n lte: `<=`,\n }\n return ops[name]!\n}\n"],"names":[],"mappings":"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"}
@@ -29,7 +29,10 @@ function compileExpression(exp, params, compileOptions) {
29
29
  );
30
30
  }
31
31
  const columnName = exp.path[0];
32
- if (compileOptions?.jsonColumn && columnName !== `id`) {
32
+ if (compileOptions?.jsonColumn && columnName === `id`) {
33
+ const prefix = compileOptions.jsonColumn.split(".")[0];
34
+ return `${prefix}.${quoteIdentifier(columnName)}`;
35
+ } else if (compileOptions?.jsonColumn) {
33
36
  return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`;
34
37
  }
35
38
  return quoteIdentifier(columnName);
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-compiler.js","sources":["../../src/sqlite-compiler.ts"],"sourcesContent":["import type { IR, LoadSubsetOptions } from '@tanstack/db'\n\n/**\n * Result of compiling LoadSubsetOptions to SQLite\n */\nexport interface SQLiteCompiledQuery {\n /** The WHERE clause (without \"WHERE\" keyword), e.g., \"price > ?\" */\n where?: string\n /** The ORDER BY clause (without \"ORDER BY\" keyword), e.g., \"price DESC\" */\n orderBy?: string\n /** The LIMIT value */\n limit?: number\n /** Parameter values in order, to be passed to SQLite query */\n params: Array<unknown>\n}\n\n/**\n * Options for controlling how SQL is compiled.\n */\nexport interface CompileSQLiteOptions {\n /**\n * When set, column references emit `json_extract(<jsonColumn>, '$.<columnName>')`\n * instead of `\"<columnName>\"`. The `id` column is excluded since it's stored\n * as a direct column in the tracked table.\n */\n jsonColumn?: string\n}\n\n/**\n * Compiles TanStack DB LoadSubsetOptions to SQLite query components.\n *\n * @example\n * ```typescript\n * const compiled = compileSQLite({\n * where: { type: 'func', name: 'gt', args: [\n * { type: 'ref', path: ['price'] },\n * { type: 'val', value: 100 }\n * ]},\n * orderBy: [{ expression: { type: 'ref', path: ['price'] }, compareOptions: { direction: 'desc', nulls: 'last' } }],\n * limit: 50\n * })\n * // Result: { where: '\"price\" > ?', orderBy: '\"price\" DESC', limit: 50, params: [100] }\n * ```\n */\nexport function compileSQLite(\n options: LoadSubsetOptions,\n compileOptions?: CompileSQLiteOptions,\n): SQLiteCompiledQuery {\n const { where, orderBy, limit } = options\n\n const params: Array<unknown> = []\n const result: SQLiteCompiledQuery = { params }\n\n if (where) {\n result.where = compileExpression(where, params, compileOptions)\n }\n\n if (orderBy) {\n result.orderBy = compileOrderBy(orderBy, params, compileOptions)\n }\n\n if (limit !== undefined) {\n result.limit = limit\n }\n\n return result\n}\n\n/**\n * Quote SQLite identifiers to handle column names correctly.\n * SQLite uses double quotes for identifiers.\n */\nfunction quoteIdentifier(name: string): string {\n // Escape any double quotes in the name by doubling them\n const escaped = name.replace(/\"/g, `\"\"`)\n return `\"${escaped}\"`\n}\n\n/**\n * Compiles a BasicExpression to a SQL string, mutating the params array.\n */\nfunction compileExpression(\n exp: IR.BasicExpression<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n switch (exp.type) {\n case `val`:\n params.push(exp.value)\n return `?`\n case `ref`: {\n if (exp.path.length !== 1) {\n throw new Error(\n `SQLite compiler doesn't support nested properties: ${exp.path.join(`.`)}`,\n )\n }\n const columnName = exp.path[0]!\n if (compileOptions?.jsonColumn && columnName !== `id`) {\n return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`\n }\n return quoteIdentifier(columnName)\n }\n case `func`:\n return compileFunction(exp, params, compileOptions)\n default:\n throw new Error(`Unknown expression type: ${(exp as any).type}`)\n }\n}\n\n/**\n * Compiles an OrderBy array to a SQL ORDER BY clause.\n */\nfunction compileOrderBy(\n orderBy: IR.OrderBy,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const clauses = orderBy.map((clause: IR.OrderByClause) =>\n compileOrderByClause(clause, params, compileOptions),\n )\n return clauses.join(`, `)\n}\n\n/**\n * Compiles a single OrderByClause to SQL.\n */\nfunction compileOrderByClause(\n clause: IR.OrderByClause,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { expression, compareOptions } = clause\n let sql = compileExpression(expression, params, compileOptions)\n\n if (compareOptions.direction === `desc`) {\n sql = `${sql} DESC`\n }\n\n // SQLite supports NULLS FIRST/LAST (since 3.30.0)\n if (compareOptions.nulls === `first`) {\n sql = `${sql} NULLS FIRST`\n } else {\n // Default to NULLS LAST (nulls === 'last')\n sql = `${sql} NULLS LAST`\n }\n\n return sql\n}\n\n/**\n * Check if a BasicExpression represents a null/undefined value\n */\nfunction isNullValue(exp: IR.BasicExpression<unknown>): boolean {\n return exp.type === `val` && (exp.value === null || exp.value === undefined)\n}\n\n/**\n * Compiles a function expression (operator) to SQL.\n */\nfunction compileFunction(\n exp: IR.Func<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { name, args } = exp\n\n // Check for null values in comparison operators\n if (isComparisonOp(name)) {\n const hasNullArg = args.some((arg: IR.BasicExpression) => isNullValue(arg))\n if (hasNullArg) {\n throw new Error(\n `Cannot use null/undefined with '${name}' operator. ` +\n `Use isNull() to check for null values.`,\n )\n }\n }\n\n // Compile arguments\n const compiledArgs = args.map((arg: IR.BasicExpression) =>\n compileExpression(arg, params, compileOptions),\n )\n\n // Handle different operator types\n switch (name) {\n // Binary comparison operators\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`${name} expects 2 arguments`)\n }\n const opSymbol = getComparisonOp(name)\n return `${compiledArgs[0]} ${opSymbol} ${compiledArgs[1]}`\n }\n\n // Logical operators\n case `and`:\n case `or`: {\n if (compiledArgs.length < 2) {\n throw new Error(`${name} expects at least 2 arguments`)\n }\n const opKeyword = name === `and` ? `AND` : `OR`\n return compiledArgs\n .map((arg: string) => `(${arg})`)\n .join(` ${opKeyword} `)\n }\n\n case `not`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`not expects 1 argument`)\n }\n // Check if argument is isNull/isUndefined for IS NOT NULL\n const arg = args[0]\n if (arg && arg.type === `func`) {\n if (arg.name === `isNull` || arg.name === `isUndefined`) {\n const innerArg = compileExpression(\n arg.args[0]!,\n params,\n compileOptions,\n )\n return `${innerArg} IS NOT NULL`\n }\n }\n return `NOT (${compiledArgs[0]})`\n }\n\n // Null checking\n case `isNull`:\n case `isUndefined`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`${name} expects 1 argument`)\n }\n return `${compiledArgs[0]} IS NULL`\n }\n\n // IN operator\n case `in`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`in expects 2 arguments (column and array)`)\n }\n // The second argument should be an array value\n // We need to handle this specially - expand the array into multiple placeholders\n const lastParamIndex = params.length - 1\n const arrayValue = params[lastParamIndex]\n\n if (!Array.isArray(arrayValue)) {\n throw new Error(`in operator requires an array value`)\n }\n\n // Remove the array param and add individual values\n params.pop()\n const placeholders = arrayValue.map(() => {\n params.push(arrayValue[params.length - lastParamIndex])\n return `?`\n })\n\n // Re-add individual values properly\n params.length = lastParamIndex // Reset to before array\n for (const val of arrayValue) {\n params.push(val)\n }\n\n return `${compiledArgs[0]} IN (${placeholders.join(`, `)})`\n }\n\n // String operators\n case `like`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`like expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]}`\n }\n\n case `ilike`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`ilike expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]} COLLATE NOCASE`\n }\n\n // String case functions\n case `upper`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`upper expects 1 argument`)\n }\n return `UPPER(${compiledArgs[0]})`\n }\n\n case `lower`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`lower expects 1 argument`)\n }\n return `LOWER(${compiledArgs[0]})`\n }\n\n case `length`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`length expects 1 argument`)\n }\n return `LENGTH(${compiledArgs[0]})`\n }\n\n case `concat`: {\n if (compiledArgs.length < 1) {\n throw new Error(`concat expects at least 1 argument`)\n }\n return `CONCAT(${compiledArgs.join(`, `)})`\n }\n\n case `add`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`add expects 2 arguments`)\n }\n return `${compiledArgs[0]} + ${compiledArgs[1]}`\n }\n\n // Null fallback\n case `coalesce`: {\n if (compiledArgs.length < 1) {\n throw new Error(`coalesce expects at least 1 argument`)\n }\n return `COALESCE(${compiledArgs.join(`, `)})`\n }\n\n default:\n throw new Error(\n `Operator '${name}' is not supported in PowerSync on-demand sync. ` +\n `Supported operators: eq, gt, gte, lt, lte, and, or, not, isNull, in, like, ilike, upper, lower, length, concat, add, coalesce`,\n )\n }\n}\n\n/**\n * Check if operator is a comparison operator\n */\nfunction isComparisonOp(name: string): boolean {\n return [`eq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`].includes(name)\n}\n\n/**\n * Get the SQL symbol for a comparison operator\n */\nfunction getComparisonOp(name: string): string {\n const ops: Record<string, string> = {\n eq: `=`,\n gt: `>`,\n gte: `>=`,\n lt: `<`,\n lte: `<=`,\n }\n return ops[name]!\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-compiler.js","sources":["../../src/sqlite-compiler.ts"],"sourcesContent":["import type { IR, LoadSubsetOptions } from '@tanstack/db'\n\n/**\n * Result of compiling LoadSubsetOptions to SQLite\n */\nexport interface SQLiteCompiledQuery {\n /** The WHERE clause (without \"WHERE\" keyword), e.g., \"price > ?\" */\n where?: string\n /** The ORDER BY clause (without \"ORDER BY\" keyword), e.g., \"price DESC\" */\n orderBy?: string\n /** The LIMIT value */\n limit?: number\n /** Parameter values in order, to be passed to SQLite query */\n params: Array<unknown>\n}\n\n/**\n * Options for controlling how SQL is compiled.\n */\nexport interface CompileSQLiteOptions {\n /**\n * When set, column references emit `json_extract(<jsonColumn>, '$.<columnName>')`\n * instead of `\"<columnName>\"`. The `id` column is excluded since it's stored\n * as a direct column in the tracked table.\n */\n jsonColumn?: string\n}\n\n/**\n * Compiles TanStack DB LoadSubsetOptions to SQLite query components.\n *\n * @example\n * ```typescript\n * const compiled = compileSQLite({\n * where: { type: 'func', name: 'gt', args: [\n * { type: 'ref', path: ['price'] },\n * { type: 'val', value: 100 }\n * ]},\n * orderBy: [{ expression: { type: 'ref', path: ['price'] }, compareOptions: { direction: 'desc', nulls: 'last' } }],\n * limit: 50\n * })\n * // Result: { where: '\"price\" > ?', orderBy: '\"price\" DESC', limit: 50, params: [100] }\n * ```\n */\nexport function compileSQLite(\n options: LoadSubsetOptions,\n compileOptions?: CompileSQLiteOptions,\n): SQLiteCompiledQuery {\n const { where, orderBy, limit } = options\n\n const params: Array<unknown> = []\n const result: SQLiteCompiledQuery = { params }\n\n if (where) {\n result.where = compileExpression(where, params, compileOptions)\n }\n\n if (orderBy) {\n result.orderBy = compileOrderBy(orderBy, params, compileOptions)\n }\n\n if (limit !== undefined) {\n result.limit = limit\n }\n\n return result\n}\n\n/**\n * Quote SQLite identifiers to handle column names correctly.\n * SQLite uses double quotes for identifiers.\n */\nfunction quoteIdentifier(name: string): string {\n // Escape any double quotes in the name by doubling them\n const escaped = name.replace(/\"/g, `\"\"`)\n return `\"${escaped}\"`\n}\n\n/**\n * Compiles a BasicExpression to a SQL string, mutating the params array.\n */\nfunction compileExpression(\n exp: IR.BasicExpression<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n switch (exp.type) {\n case `val`:\n params.push(exp.value)\n return `?`\n case `ref`: {\n if (exp.path.length !== 1) {\n throw new Error(\n `SQLite compiler doesn't support nested properties: ${exp.path.join(`.`)}`,\n )\n }\n const columnName = exp.path[0]!\n\n // PowerSync stores `id` as a top-level row column rather than inside the\n // JSON `data` object, so we skip json_extract. However, when compiling for\n // trigger WHEN clauses we still need the OLD./NEW. prefix. Extract it from\n // the jsonColumn option.\n if (compileOptions?.jsonColumn && columnName === `id`) {\n const prefix = compileOptions.jsonColumn.split('.')[0]!\n return `${prefix}.${quoteIdentifier(columnName)}`\n } else if (compileOptions?.jsonColumn) {\n return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`\n }\n return quoteIdentifier(columnName)\n }\n case `func`:\n return compileFunction(exp, params, compileOptions)\n default:\n throw new Error(`Unknown expression type: ${(exp as any).type}`)\n }\n}\n\n/**\n * Compiles an OrderBy array to a SQL ORDER BY clause.\n */\nfunction compileOrderBy(\n orderBy: IR.OrderBy,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const clauses = orderBy.map((clause: IR.OrderByClause) =>\n compileOrderByClause(clause, params, compileOptions),\n )\n return clauses.join(`, `)\n}\n\n/**\n * Compiles a single OrderByClause to SQL.\n */\nfunction compileOrderByClause(\n clause: IR.OrderByClause,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { expression, compareOptions } = clause\n let sql = compileExpression(expression, params, compileOptions)\n\n if (compareOptions.direction === `desc`) {\n sql = `${sql} DESC`\n }\n\n // SQLite supports NULLS FIRST/LAST (since 3.30.0)\n if (compareOptions.nulls === `first`) {\n sql = `${sql} NULLS FIRST`\n } else {\n // Default to NULLS LAST (nulls === 'last')\n sql = `${sql} NULLS LAST`\n }\n\n return sql\n}\n\n/**\n * Check if a BasicExpression represents a null/undefined value\n */\nfunction isNullValue(exp: IR.BasicExpression<unknown>): boolean {\n return exp.type === `val` && (exp.value === null || exp.value === undefined)\n}\n\n/**\n * Compiles a function expression (operator) to SQL.\n */\nfunction compileFunction(\n exp: IR.Func<unknown>,\n params: Array<unknown>,\n compileOptions?: CompileSQLiteOptions,\n): string {\n const { name, args } = exp\n\n // Check for null values in comparison operators\n if (isComparisonOp(name)) {\n const hasNullArg = args.some((arg: IR.BasicExpression) => isNullValue(arg))\n if (hasNullArg) {\n throw new Error(\n `Cannot use null/undefined with '${name}' operator. ` +\n `Use isNull() to check for null values.`,\n )\n }\n }\n\n // Compile arguments\n const compiledArgs = args.map((arg: IR.BasicExpression) =>\n compileExpression(arg, params, compileOptions),\n )\n\n // Handle different operator types\n switch (name) {\n // Binary comparison operators\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`${name} expects 2 arguments`)\n }\n const opSymbol = getComparisonOp(name)\n return `${compiledArgs[0]} ${opSymbol} ${compiledArgs[1]}`\n }\n\n // Logical operators\n case `and`:\n case `or`: {\n if (compiledArgs.length < 2) {\n throw new Error(`${name} expects at least 2 arguments`)\n }\n const opKeyword = name === `and` ? `AND` : `OR`\n return compiledArgs\n .map((arg: string) => `(${arg})`)\n .join(` ${opKeyword} `)\n }\n\n case `not`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`not expects 1 argument`)\n }\n // Check if argument is isNull/isUndefined for IS NOT NULL\n const arg = args[0]\n if (arg && arg.type === `func`) {\n if (arg.name === `isNull` || arg.name === `isUndefined`) {\n const innerArg = compileExpression(\n arg.args[0]!,\n params,\n compileOptions,\n )\n return `${innerArg} IS NOT NULL`\n }\n }\n return `NOT (${compiledArgs[0]})`\n }\n\n // Null checking\n case `isNull`:\n case `isUndefined`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`${name} expects 1 argument`)\n }\n return `${compiledArgs[0]} IS NULL`\n }\n\n // IN operator\n case `in`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`in expects 2 arguments (column and array)`)\n }\n // The second argument should be an array value\n // We need to handle this specially - expand the array into multiple placeholders\n const lastParamIndex = params.length - 1\n const arrayValue = params[lastParamIndex]\n\n if (!Array.isArray(arrayValue)) {\n throw new Error(`in operator requires an array value`)\n }\n\n // Remove the array param and add individual values\n params.pop()\n const placeholders = arrayValue.map(() => {\n params.push(arrayValue[params.length - lastParamIndex])\n return `?`\n })\n\n // Re-add individual values properly\n params.length = lastParamIndex // Reset to before array\n for (const val of arrayValue) {\n params.push(val)\n }\n\n return `${compiledArgs[0]} IN (${placeholders.join(`, `)})`\n }\n\n // String operators\n case `like`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`like expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]}`\n }\n\n case `ilike`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`ilike expects 2 arguments`)\n }\n return `${compiledArgs[0]} LIKE ${compiledArgs[1]} COLLATE NOCASE`\n }\n\n // String case functions\n case `upper`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`upper expects 1 argument`)\n }\n return `UPPER(${compiledArgs[0]})`\n }\n\n case `lower`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`lower expects 1 argument`)\n }\n return `LOWER(${compiledArgs[0]})`\n }\n\n case `length`: {\n if (compiledArgs.length !== 1) {\n throw new Error(`length expects 1 argument`)\n }\n return `LENGTH(${compiledArgs[0]})`\n }\n\n case `concat`: {\n if (compiledArgs.length < 1) {\n throw new Error(`concat expects at least 1 argument`)\n }\n return `CONCAT(${compiledArgs.join(`, `)})`\n }\n\n case `add`: {\n if (compiledArgs.length !== 2) {\n throw new Error(`add expects 2 arguments`)\n }\n return `${compiledArgs[0]} + ${compiledArgs[1]}`\n }\n\n // Null fallback\n case `coalesce`: {\n if (compiledArgs.length < 1) {\n throw new Error(`coalesce expects at least 1 argument`)\n }\n return `COALESCE(${compiledArgs.join(`, `)})`\n }\n\n default:\n throw new Error(\n `Operator '${name}' is not supported in PowerSync on-demand sync. ` +\n `Supported operators: eq, gt, gte, lt, lte, and, or, not, isNull, in, like, ilike, upper, lower, length, concat, add, coalesce`,\n )\n }\n}\n\n/**\n * Check if operator is a comparison operator\n */\nfunction isComparisonOp(name: string): boolean {\n return [`eq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`].includes(name)\n}\n\n/**\n * Get the SQL symbol for a comparison operator\n */\nfunction getComparisonOp(name: string): string {\n const ops: Record<string, string> = {\n eq: `=`,\n gt: `>`,\n gte: `>=`,\n lt: `<`,\n lte: `<=`,\n }\n return ops[name]!\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/powersync-db-collection",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "PowerSync collection for TanStack DB",
5
5
  "author": "POWERSYNC",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "@tanstack/store": "^0.9.2",
51
51
  "debug": "^4.4.3",
52
52
  "p-defer": "^4.0.1",
53
- "@tanstack/db": "0.6.4"
53
+ "@tanstack/db": "0.6.6"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@powersync/common": "^1.41.0"
@@ -95,7 +95,15 @@ function compileExpression(
95
95
  )
96
96
  }
97
97
  const columnName = exp.path[0]!
98
- if (compileOptions?.jsonColumn && columnName !== `id`) {
98
+
99
+ // PowerSync stores `id` as a top-level row column rather than inside the
100
+ // JSON `data` object, so we skip json_extract. However, when compiling for
101
+ // trigger WHEN clauses we still need the OLD./NEW. prefix. Extract it from
102
+ // the jsonColumn option.
103
+ if (compileOptions?.jsonColumn && columnName === `id`) {
104
+ const prefix = compileOptions.jsonColumn.split('.')[0]!
105
+ return `${prefix}.${quoteIdentifier(columnName)}`
106
+ } else if (compileOptions?.jsonColumn) {
99
107
  return `json_extract(${compileOptions.jsonColumn}, '$.${columnName}')`
100
108
  }
101
109
  return quoteIdentifier(columnName)