@joystick.js/db-canary 0.0.0-canary.2249 → 0.0.0-canary.2251

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/server/lib/operations/find.js +1 -1
  2. package/dist/server/lib/operations/find_one.js +1 -1
  3. package/package.json +2 -2
  4. package/src/server/lib/operations/find.js +157 -14
  5. package/src/server/lib/operations/find_one.js +157 -13
  6. package/test_data_api_key_1758233848259_cglfjzhou/data.mdb +0 -0
  7. package/test_data_api_key_1758233848259_cglfjzhou/lock.mdb +0 -0
  8. package/test_data_api_key_1758233848502_urlje2utd/data.mdb +0 -0
  9. package/test_data_api_key_1758233848502_urlje2utd/lock.mdb +0 -0
  10. package/test_data_api_key_1758233848738_mtcpfe5ns/data.mdb +0 -0
  11. package/test_data_api_key_1758233848738_mtcpfe5ns/lock.mdb +0 -0
  12. package/test_data_api_key_1758233848856_9g97p6gag/data.mdb +0 -0
  13. package/test_data_api_key_1758233848856_9g97p6gag/lock.mdb +0 -0
  14. package/test_data_api_key_1758233857008_0tl9zzhj8/data.mdb +0 -0
  15. package/test_data_api_key_1758233857008_0tl9zzhj8/lock.mdb +0 -0
  16. package/test_data_api_key_1758233857120_60c2f2uhu/data.mdb +0 -0
  17. package/test_data_api_key_1758233857120_60c2f2uhu/lock.mdb +0 -0
  18. package/test_data_api_key_1758233857232_aw7fkqgd9/data.mdb +0 -0
  19. package/test_data_api_key_1758233857232_aw7fkqgd9/lock.mdb +0 -0
  20. package/test_data_api_key_1758234881285_4aeflubjb/data.mdb +0 -0
  21. package/test_data_api_key_1758234881285_4aeflubjb/lock.mdb +0 -0
  22. package/test_data_api_key_1758234881520_kb0amvtqb/data.mdb +0 -0
  23. package/test_data_api_key_1758234881520_kb0amvtqb/lock.mdb +0 -0
  24. package/test_data_api_key_1758234881756_k04gfv2va/data.mdb +0 -0
  25. package/test_data_api_key_1758234881756_k04gfv2va/lock.mdb +0 -0
  26. package/test_data_api_key_1758234881876_wn90dpo1z/data.mdb +0 -0
  27. package/test_data_api_key_1758234881876_wn90dpo1z/lock.mdb +0 -0
  28. package/test_data_api_key_1758234889461_26xz3dmbr/data.mdb +0 -0
  29. package/test_data_api_key_1758234889461_26xz3dmbr/lock.mdb +0 -0
  30. package/test_data_api_key_1758234889572_uziz7e0p5/data.mdb +0 -0
  31. package/test_data_api_key_1758234889572_uziz7e0p5/lock.mdb +0 -0
  32. package/test_data_api_key_1758234889684_5f9wmposh/data.mdb +0 -0
  33. package/test_data_api_key_1758234889684_5f9wmposh/lock.mdb +0 -0
  34. package/test_data_api_key_1758235657729_prwgm6mxr/data.mdb +0 -0
  35. package/test_data_api_key_1758235657729_prwgm6mxr/lock.mdb +0 -0
  36. package/test_data_api_key_1758235657961_rc2da0dc2/data.mdb +0 -0
  37. package/test_data_api_key_1758235657961_rc2da0dc2/lock.mdb +0 -0
  38. package/test_data_api_key_1758235658193_oqqxm0sny/data.mdb +0 -0
  39. package/test_data_api_key_1758235658193_oqqxm0sny/lock.mdb +0 -0
  40. package/test_data_api_key_1758235658309_vggac1pj6/data.mdb +0 -0
  41. package/test_data_api_key_1758235658309_vggac1pj6/lock.mdb +0 -0
  42. package/test_data_api_key_1758235665968_61ko07dd1/data.mdb +0 -0
  43. package/test_data_api_key_1758235665968_61ko07dd1/lock.mdb +0 -0
  44. package/test_data_api_key_1758235666082_50lrt6sq8/data.mdb +0 -0
  45. package/test_data_api_key_1758235666082_50lrt6sq8/lock.mdb +0 -0
  46. package/test_data_api_key_1758235666194_ykvauwlzh/data.mdb +0 -0
  47. package/test_data_api_key_1758235666194_ykvauwlzh/lock.mdb +0 -0
  48. package/test_data_api_key_1758236187207_9c4paeh09/data.mdb +0 -0
  49. package/test_data_api_key_1758236187207_9c4paeh09/lock.mdb +0 -0
  50. package/test_data_api_key_1758236187441_4n3o3gkkl/data.mdb +0 -0
  51. package/test_data_api_key_1758236187441_4n3o3gkkl/lock.mdb +0 -0
  52. package/test_data_api_key_1758236187672_jt6b21ye0/data.mdb +0 -0
  53. package/test_data_api_key_1758236187672_jt6b21ye0/lock.mdb +0 -0
  54. package/test_data_api_key_1758236187788_oo84fz9u6/data.mdb +0 -0
  55. package/test_data_api_key_1758236187788_oo84fz9u6/lock.mdb +0 -0
  56. package/test_data_api_key_1758236195507_o9zeznwlm/data.mdb +0 -0
  57. package/test_data_api_key_1758236195507_o9zeznwlm/lock.mdb +0 -0
  58. package/test_data_api_key_1758236195619_qsqd60y41/data.mdb +0 -0
  59. package/test_data_api_key_1758236195619_qsqd60y41/lock.mdb +0 -0
  60. package/test_data_api_key_1758236195731_im13iq284/data.mdb +0 -0
  61. package/test_data_api_key_1758236195731_im13iq284/lock.mdb +0 -0
  62. package/tests/server/cluster/master_read_write_operations.test.js +5 -14
  63. package/tests/server/integration/authentication_integration.test.js +18 -10
  64. package/tests/server/integration/backup_integration.test.js +35 -27
  65. package/tests/server/lib/api_key_manager.test.js +88 -32
  66. package/tests/server/lib/development_mode.test.js +2 -2
  67. package/tests/server/lib/operations/admin.test.js +20 -12
  68. package/tests/server/lib/operations/delete_one.test.js +10 -4
  69. package/tests/server/lib/operations/find_array_queries.test.js +261 -0
@@ -1 +1 @@
1
- import{get_database as D,build_collection_key as A}from"../query_engine.js";import{can_use_index as J,find_documents_by_index as v}from"../index_manager.js";import{record_query as N,record_index_usage as F}from"../auto_index_manager.js";import S from"../logger.js";const{create_context_logger:R}=S("find"),C=(n,t)=>{const o=t.split(".");let r=n;for(const e of o){if(r==null)return;r=r[e]}return r},P=(n,t)=>{const o=t.split(".");let r=n;for(let e=0;e<o.length;e++){if(r==null||typeof r!="object")return!1;if(e===o.length-1)return r.hasOwnProperty(o[e]);r=r[o[e]]}return!1},$=(n,t)=>{if(!t||Object.keys(t).length===0)return!0;for(const[o,r]of Object.entries(t)){const e=C(n,o);if(typeof r=="object"&&r!==null&&!Array.isArray(r))for(const[i,s]of Object.entries(r))switch(i){case"$eq":if(e!==s)return!1;break;case"$ne":if(e===s)return!1;break;case"$gt":if(e<=s)return!1;break;case"$gte":if(e<s)return!1;break;case"$lt":if(e>=s)return!1;break;case"$lte":if(e>s)return!1;break;case"$in":if(!Array.isArray(s)||!s.includes(e))return!1;break;case"$nin":if(!Array.isArray(s)||s.includes(e))return!1;break;case"$exists":const c=P(n,o);if(s&&!c||!s&&c)return!1;break;case"$regex":const u=r.$options||"";if(!new RegExp(s,u).test(e))return!1;break;case"$options":break;default:throw new Error(`Unsupported query operator: ${i}`)}else if(e!==r)return!1}return!0},U=(n,t)=>{if(!t||Object.keys(t).length===0)return n;const o=Object.values(t).some(e=>e===1||e===!0),r={};if(o){r._id=n._id;for(const[e,i]of Object.entries(t))e==="_id"&&(i===0||i===!1)?delete r._id:(i===1||i===!0)&&(r[e]=n[e])}else{Object.assign(r,n);for(const[e,i]of Object.entries(t))(i===0||i===!1)&&delete r[e]}return r},z=(n,t)=>!t||Object.keys(t).length===0?n:n.sort((o,r)=>{for(const[e,i]of Object.entries(t)){const s=o[e],c=r[e];if(s===c)continue;if(s===void 0)return 1;if(c===void 0)return-1;const u=s<c?-1:s>c?1:0;return i===-1?-u:u}return 0}),B=async(n,t,o={},r={})=>{const e=R();if(!n)throw new Error("Database name is required");if(!t)throw new Error("Collection name is required");const i=D(),{projection:s,sort:c,limit:u,skip:h=0}=r,E=Date.now();try{let d=[],_=!1,k=null;const O=J(n,t,o);if(O){const{field:f,operators:b}=O,a=o[f];if(k=f,typeof a=="object"&&a!==null&&!Array.isArray(a)){for(const l of b)if(a[l]!==void 0){const p=v(n,t,f,l,a[l]);if(p){_=!0,F(n,t,f);for(const m of p){const x=A(n,t,m),y=i.get(x);if(y){const q=JSON.parse(y);$(q,o)&&d.push(q)}}break}}}else if(b.includes("eq")){const l=v(n,t,f,"eq",a);if(l){_=!0,F(n,t,f);for(const p of l){const m=A(n,t,p),x=i.get(m);if(x){const y=JSON.parse(x);$(y,o)&&d.push(y)}}}}}if(!_){const f=`${n}:${t}:`,b=i.getRange({start:f,end:f+"\xFF"});for(const{key:a,value:l}of b){const p=JSON.parse(l);$(p,o)&&d.push(p)}}let g=z(d,c);h>0&&(g=g.slice(h)),u&&u>0&&(g=g.slice(0,u));const j=g.map(f=>U(f,s)),w=Date.now()-E;try{N(t,o,w,_,k)}catch(f){e.warn("Failed to record query for auto-indexing",{error:f.message})}return e.info("Find operation completed",{database:n,collection:t,documents_found:j.length,total_matching:d.length,used_index:_,indexed_field:k,execution_time_ms:w}),j}catch(d){throw e.error("Failed to find documents",{database:n,collection:t,error:d.message}),d}};var L=B;export{L as default};
1
+ import{get_database as J,build_collection_key as q}from"../query_engine.js";import{can_use_index as N,find_documents_by_index as F}from"../index_manager.js";import{record_query as S,record_index_usage as E}from"../auto_index_manager.js";import R from"../logger.js";const{create_context_logger:C}=R("find"),D=(i,t)=>{const o=t.split(".");let r=i;for(let e=0;e<o.length;e++){const l=o[e];if(r==null)return;if(r=r[l],Array.isArray(r)&&e<o.length-1){const n=o.slice(e+1).join("."),u=[];for(let c=0;c<r.length;c++){const d=r[c];if(typeof d=="object"&&d!==null){const f=D(d,n);f!==void 0&&(Array.isArray(f)?u.push(...f):u.push(f))}}return u.length>0?u:void 0}}return r},P=(i,t)=>{const o=t.split(".");let r=i;for(let e=0;e<o.length;e++){if(r==null||typeof r!="object")return!1;if(e===o.length-1)return r.hasOwnProperty(o[e]);r=r[o[e]]}return!1},b=(i,t)=>{if(!t||Object.keys(t).length===0)return!0;if(t.$or&&Array.isArray(t.$or)){const o=t.$or;let r=!1;for(let l=0;l<o.length;l++)if(b(i,o[l])){r=!0;break}if(!r)return!1;const e={...t};return delete e.$or,Object.keys(e).length>0?b(i,e):!0}for(const[o,r]of Object.entries(t)){const e=D(i,o);if(typeof r=="object"&&r!==null&&!Array.isArray(r))for(const[l,n]of Object.entries(r))switch(l){case"$eq":if(Array.isArray(e)){if(!e.includes(n))return!1}else if(e!==n)return!1;break;case"$ne":if(Array.isArray(e)){if(e.includes(n))return!1}else if(e===n)return!1;break;case"$gt":if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(e[s]>n){f=!0;break}if(!f)return!1}else if(e<=n)return!1;break;case"$gte":if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(e[s]>=n){f=!0;break}if(!f)return!1}else if(e<n)return!1;break;case"$lt":if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(e[s]<n){f=!0;break}if(!f)return!1}else if(e>=n)return!1;break;case"$lte":if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(e[s]<=n){f=!0;break}if(!f)return!1}else if(e>n)return!1;break;case"$in":if(!Array.isArray(n))return!1;if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(n.includes(e[s])){f=!0;break}if(!f)return!1}else if(!n.includes(e))return!1;break;case"$nin":if(!Array.isArray(n))return!1;if(Array.isArray(e)){for(let f=0;f<e.length;f++)if(n.includes(e[f]))return!1}else if(n.includes(e))return!1;break;case"$exists":const u=P(i,o);if(n&&!u||!n&&u)return!1;break;case"$regex":const c=r.$options||"",d=new RegExp(n,c);if(Array.isArray(e)){let f=!1;for(let s=0;s<e.length;s++)if(typeof e[s]=="string"&&d.test(e[s])){f=!0;break}if(!f)return!1}else if(!d.test(e))return!1;break;case"$options":break;default:throw new Error(`Unsupported query operator: ${l}`)}else if(Array.isArray(e)){if(!e.includes(r))return!1}else if(e!==r)return!1}return!0},U=(i,t)=>{if(!t||Object.keys(t).length===0)return i;const o=Object.values(t).some(e=>e===1||e===!0),r={};if(o){r._id=i._id;for(const[e,l]of Object.entries(t))e==="_id"&&(l===0||l===!1)?delete r._id:(l===1||l===!0)&&(r[e]=i[e])}else{Object.assign(r,i);for(const[e,l]of Object.entries(t))(l===0||l===!1)&&delete r[e]}return r},z=(i,t)=>!t||Object.keys(t).length===0?i:i.sort((o,r)=>{for(const[e,l]of Object.entries(t)){const n=o[e],u=r[e];if(n===u)continue;if(n===void 0)return 1;if(u===void 0)return-1;const c=n<u?-1:n>u?1:0;return l===-1?-c:c}return 0}),B=async(i,t,o={},r={})=>{const e=C();if(!i)throw new Error("Database name is required");if(!t)throw new Error("Collection name is required");const l=J(),{projection:n,sort:u,limit:c,skip:d=0}=r,f=Date.now();try{let s=[],_=!1,$=null;const m=N(i,t,o);if(m){const{field:a,operators:k}=m,g=o[a];if($=a,typeof g=="object"&&g!==null&&!Array.isArray(g)){for(const y of k)if(g[y]!==void 0){const p=F(i,t,a,y,g[y]);if(p){_=!0,E(i,t,a);for(const j of p){const x=q(i,t,j),A=l.get(x);if(A){const v=JSON.parse(A);b(v,o)&&s.push(v)}}break}}}else if(k.includes("eq")){const y=F(i,t,a,"eq",g);if(y){_=!0,E(i,t,a);for(const p of y){const j=q(i,t,p),x=l.get(j);if(x){const A=JSON.parse(x);b(A,o)&&s.push(A)}}}}}if(!_){const a=`${i}:${t}:`,k=l.getRange({start:a,end:a+"\xFF"});for(const{key:g,value:y}of k){const p=JSON.parse(y);b(p,o)&&s.push(p)}}let h=z(s,u);d>0&&(h=h.slice(d)),c&&c>0&&(h=h.slice(0,c));const O=h.map(a=>U(a,n)),w=Date.now()-f;try{S(t,o,w,_,$)}catch(a){e.warn("Failed to record query for auto-indexing",{error:a.message})}return e.info("Find operation completed",{database:i,collection:t,documents_found:O.length,total_matching:s.length,used_index:_,indexed_field:$,execution_time_ms:w}),O}catch(s){throw e.error("Failed to find documents",{database:i,collection:t,error:s.message}),s}};var L=B;export{L as default};
@@ -1 +1 @@
1
- import{get_database as A,build_collection_key as j}from"../query_engine.js";import{can_use_index as v,find_documents_by_index as m}from"../index_manager.js";import{record_query as D,record_index_usage as q}from"../auto_index_manager.js";import E from"../logger.js";const{create_context_logger:F}=E("find_one"),N=(o,t)=>{const n=t.split(".");let r=o;for(const e of n){if(r==null)return;r=r[e]}return r},J=(o,t)=>{const n=t.split(".");let r=o;for(let e=0;e<n.length;e++){if(r==null||typeof r!="object")return!1;if(e===n.length-1)return r.hasOwnProperty(n[e]);r=r[n[e]]}return!1},w=(o,t)=>{if(!t||Object.keys(t).length===0)return!0;for(const[n,r]of Object.entries(t)){const e=N(o,n);if(typeof r=="object"&&r!==null&&!Array.isArray(r))for(const[i,s]of Object.entries(r))switch(i){case"$eq":if(e!==s)return!1;break;case"$ne":if(e===s)return!1;break;case"$gt":if(e<=s)return!1;break;case"$gte":if(e<s)return!1;break;case"$lt":if(e>=s)return!1;break;case"$lte":if(e>s)return!1;break;case"$in":if(!Array.isArray(s)||!s.includes(e))return!1;break;case"$nin":if(!Array.isArray(s)||s.includes(e))return!1;break;case"$exists":const x=J(o,n);if(s&&!x||!s&&x)return!1;break;case"$regex":const b=r.$options||"";if(!new RegExp(s,b).test(e))return!1;break;case"$options":break;default:throw new Error(`Unsupported query operator: ${i}`)}else if(e!==r)return!1}return!0},S=(o,t)=>{if(!t||Object.keys(t).length===0)return o;const n=Object.values(t).some(e=>e===1||e===!0),r={};if(n){r._id=o._id;for(const[e,i]of Object.entries(t))e==="_id"&&(i===0||i===!1)?delete r._id:(i===1||i===!0)&&(r[e]=o[e])}else{Object.assign(r,o);for(const[e,i]of Object.entries(t))(i===0||i===!1)&&delete r[e]}return r},R=async(o,t,n={},r={})=>{const e=F();if(!o)throw new Error("Database name is required");if(!t)throw new Error("Collection name is required");const i=A(),{projection:s,sort:x}=r,b=Date.now();try{let c=null,a=!1,_=null;const O=v(o,t,n);if(O){const{field:f,operators:g}=O,d=n[f];if(_=f,typeof d=="object"&&d!==null&&!Array.isArray(d)){for(const u of g)if(d[u]!==void 0){const l=m(o,t,f,u,d[u]);if(l&&l.length>0){a=!0,q(o,t,f);for(const h of l){const y=j(o,t,h),p=i.get(y);if(p)try{const $=JSON.parse(p);if(w($,n)){c=$;break}}catch{continue}}break}}}else if(g.includes("eq")){const u=m(o,t,f,"eq",d);if(u&&u.length>0){a=!0,q(o,t,f);for(const l of u){const h=j(o,t,l),y=i.get(h);if(y)try{const p=JSON.parse(y);if(w(p,n)){c=p;break}}catch{continue}}}}}if(!a){const f=`${o}:${t}:`,g=i.getRange({start:f,end:f+"\xFF"});for(const{key:d,value:u}of g)try{const l=JSON.parse(u);if(w(l,n)){c=l;break}}catch{continue}}const k=Date.now()-b;try{D(t,n,k,a,_)}catch(f){e.warn("Failed to record query for auto-indexing",{error:f.message})}if(c){const f=S(c,s);return e.info("Document found",{database:o,collection:t,document_id:c._id,used_index:a,indexed_field:_,execution_time_ms:k}),f}return e.info("No document found",{database:o,collection:t,used_index:a,indexed_field:_,execution_time_ms:k}),null}catch(c){throw e.error("Failed to find document",{database:o,collection:t,error:c.message}),c}};var B=R;export{B as default};
1
+ import{get_database as q,build_collection_key as w}from"../query_engine.js";import{can_use_index as D,find_documents_by_index as O}from"../index_manager.js";import{record_query as E,record_index_usage as m}from"../auto_index_manager.js";import F from"../logger.js";const{create_context_logger:N}=F("find_one"),v=(i,s)=>{const o=s.split(".");let r=i;for(let e=0;e<o.length;e++){const l=o[e];if(r==null)return;if(r=r[l],Array.isArray(r)&&e<o.length-1){const f=o.slice(e+1).join("."),y=[];for(let g=0;g<r.length;g++){const u=r[g];if(typeof u=="object"&&u!==null){const t=v(u,f);t!==void 0&&(Array.isArray(t)?y.push(...t):y.push(t))}}return y.length>0?y:void 0}}return r},J=(i,s)=>{const o=s.split(".");let r=i;for(let e=0;e<o.length;e++){if(r==null||typeof r!="object")return!1;if(e===o.length-1)return r.hasOwnProperty(o[e]);r=r[o[e]]}return!1},h=(i,s)=>{if(!s||Object.keys(s).length===0)return!0;if(s.$or&&Array.isArray(s.$or)){const o=s.$or;let r=!1;for(let l=0;l<o.length;l++)if(h(i,o[l])){r=!0;break}if(!r)return!1;const e={...s};return delete e.$or,Object.keys(e).length>0?h(i,e):!0}for(const[o,r]of Object.entries(s)){const e=v(i,o);if(typeof r=="object"&&r!==null&&!Array.isArray(r))for(const[l,f]of Object.entries(r))switch(l){case"$eq":if(Array.isArray(e)){if(!e.includes(f))return!1}else if(e!==f)return!1;break;case"$ne":if(Array.isArray(e)){if(e.includes(f))return!1}else if(e===f)return!1;break;case"$gt":if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(e[n]>f){t=!0;break}if(!t)return!1}else if(e<=f)return!1;break;case"$gte":if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(e[n]>=f){t=!0;break}if(!t)return!1}else if(e<f)return!1;break;case"$lt":if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(e[n]<f){t=!0;break}if(!t)return!1}else if(e>=f)return!1;break;case"$lte":if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(e[n]<=f){t=!0;break}if(!t)return!1}else if(e>f)return!1;break;case"$in":if(!Array.isArray(f))return!1;if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(f.includes(e[n])){t=!0;break}if(!t)return!1}else if(!f.includes(e))return!1;break;case"$nin":if(!Array.isArray(f))return!1;if(Array.isArray(e)){for(let t=0;t<e.length;t++)if(f.includes(e[t]))return!1}else if(f.includes(e))return!1;break;case"$exists":const y=J(i,o);if(f&&!y||!f&&y)return!1;break;case"$regex":const g=r.$options||"",u=new RegExp(f,g);if(Array.isArray(e)){let t=!1;for(let n=0;n<e.length;n++)if(typeof e[n]=="string"&&u.test(e[n])){t=!0;break}if(!t)return!1}else if(!u.test(e))return!1;break;case"$options":break;default:throw new Error(`Unsupported query operator: ${l}`)}else if(Array.isArray(e)){if(!e.includes(r))return!1}else if(e!==r)return!1}return!0},S=(i,s)=>{if(!s||Object.keys(s).length===0)return i;const o=Object.values(s).some(e=>e===1||e===!0),r={};if(o){r._id=i._id;for(const[e,l]of Object.entries(s))e==="_id"&&(l===0||l===!1)?delete r._id:(l===1||l===!0)&&(r[e]=i[e])}else{Object.assign(r,i);for(const[e,l]of Object.entries(s))(l===0||l===!1)&&delete r[e]}return r},R=async(i,s,o={},r={})=>{const e=N();if(!i)throw new Error("Database name is required");if(!s)throw new Error("Collection name is required");const l=q(),{projection:f,sort:y}=r,g=Date.now();try{let u=null,t=!1,n=null;const j=D(i,s,o);if(j){const{field:a,operators:A}=j,p=o[a];if(n=a,typeof p=="object"&&p!==null&&!Array.isArray(p)){for(const c of A)if(p[c]!==void 0){const d=O(i,s,a,c,p[c]);if(d&&d.length>0){t=!0,m(i,s,a);for(const x of d){const b=w(i,s,x),_=l.get(b);if(_)try{const $=JSON.parse(_);if(h($,o)){u=$;break}}catch{continue}}break}}}else if(A.includes("eq")){const c=O(i,s,a,"eq",p);if(c&&c.length>0){t=!0,m(i,s,a);for(const d of c){const x=w(i,s,d),b=l.get(x);if(b)try{const _=JSON.parse(b);if(h(_,o)){u=_;break}}catch{continue}}}}}if(!t){const a=`${i}:${s}:`,A=l.getRange({start:a,end:a+"\xFF"});for(const{key:p,value:c}of A)try{const d=JSON.parse(c);if(h(d,o)){u=d;break}}catch{continue}}const k=Date.now()-g;try{E(s,o,k,t,n)}catch(a){e.warn("Failed to record query for auto-indexing",{error:a.message})}if(u){const a=S(u,f);return e.info("Document found",{database:i,collection:s,document_id:u._id,used_index:t,indexed_field:n,execution_time_ms:k}),a}return e.info("No document found",{database:i,collection:s,used_index:t,indexed_field:n,execution_time_ms:k}),null}catch(u){throw e.error("Failed to find document",{database:i,collection:s,error:u.message}),u}};var B=R;export{B as default};
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@joystick.js/db-canary",
3
3
  "type": "module",
4
- "version": "0.0.0-canary.2249",
5
- "canary_version": "0.0.0-canary.2248",
4
+ "version": "0.0.0-canary.2251",
5
+ "canary_version": "0.0.0-canary.2250",
6
6
  "description": "JoystickDB - A minimalist database server for the Joystick framework",
7
7
  "main": "./dist/server/index.js",
8
8
  "scripts": {
@@ -13,19 +13,48 @@ const { create_context_logger } = create_logger('find');
13
13
 
14
14
  /**
15
15
  * Extracts field value from document using dot notation path.
16
+ * Handles nested array object queries like 'reviews.rating' where reviews is an array of objects.
16
17
  * @param {Object} document - Document to extract value from
17
- * @param {string} field_path - Dot-separated field path (e.g., 'user.profile.name')
18
- * @returns {*} Field value or undefined if path doesn't exist
18
+ * @param {string} field_path - Dot-separated field path (e.g., 'user.profile.name', 'reviews.rating')
19
+ * @returns {*} Field value, array of values for nested array queries, or undefined if path doesn't exist
19
20
  */
20
21
  const get_field_value = (document, field_path) => {
21
22
  const parts = field_path.split('.');
22
23
  let value = document;
23
24
 
24
- for (const part of parts) {
25
+ for (let i = 0; i < parts.length; i++) {
26
+ const part = parts[i];
27
+
25
28
  if (value === null || value === undefined) {
26
29
  return undefined;
27
30
  }
31
+
28
32
  value = value[part];
33
+
34
+ // If we got an array and there are more parts to traverse
35
+ if (Array.isArray(value) && i < parts.length - 1) {
36
+ // Extract the remaining path
37
+ const remaining_path = parts.slice(i + 1).join('.');
38
+
39
+ // Collect all matching values from array elements
40
+ const nested_values = [];
41
+ for (let j = 0; j < value.length; j++) {
42
+ const item = value[j];
43
+ if (typeof item === 'object' && item !== null) {
44
+ const nested_value = get_field_value(item, remaining_path);
45
+ if (nested_value !== undefined) {
46
+ if (Array.isArray(nested_value)) {
47
+ nested_values.push(...nested_value);
48
+ } else {
49
+ nested_values.push(nested_value);
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ // Return array of values for nested array queries
56
+ return nested_values.length > 0 ? nested_values : undefined;
57
+ }
29
58
  }
30
59
 
31
60
  return value;
@@ -58,7 +87,7 @@ const field_exists = (document, field_path) => {
58
87
 
59
88
  /**
60
89
  * Checks if a document matches the provided filter criteria.
61
- * Supports MongoDB-like query operators including $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex.
90
+ * Supports MongoDB-like query operators with optimized array field matching.
62
91
  * @param {Object} document - Document to test against filter
63
92
  * @param {Object} filter - Filter criteria with field names and values/operators
64
93
  * @returns {boolean} True if document matches filter, false otherwise
@@ -69,6 +98,27 @@ const matches_filter = (document, filter) => {
69
98
  return true;
70
99
  }
71
100
 
101
+ // Handle $or operator at the top level
102
+ if (filter.$or && Array.isArray(filter.$or)) {
103
+ const or_conditions = filter.$or;
104
+ let or_match = false;
105
+ for (let i = 0; i < or_conditions.length; i++) {
106
+ if (matches_filter(document, or_conditions[i])) {
107
+ or_match = true;
108
+ break;
109
+ }
110
+ }
111
+ if (!or_match) return false;
112
+
113
+ // Continue checking other conditions (if any) outside of $or
114
+ const remaining_filter = { ...filter };
115
+ delete remaining_filter.$or;
116
+ if (Object.keys(remaining_filter).length > 0) {
117
+ return matches_filter(document, remaining_filter);
118
+ }
119
+ return true;
120
+ }
121
+
72
122
  for (const [field, value] of Object.entries(filter)) {
73
123
  const field_value = get_field_value(document, field);
74
124
 
@@ -76,28 +126,105 @@ const matches_filter = (document, filter) => {
76
126
  for (const [operator, operand] of Object.entries(value)) {
77
127
  switch (operator) {
78
128
  case '$eq':
79
- if (field_value !== operand) return false;
129
+ // Handle array field matching for $eq
130
+ if (Array.isArray(field_value)) {
131
+ if (!field_value.includes(operand)) return false;
132
+ } else if (field_value !== operand) {
133
+ return false;
134
+ }
80
135
  break;
81
136
  case '$ne':
82
- if (field_value === operand) return false;
137
+ // Handle array field matching for $ne
138
+ if (Array.isArray(field_value)) {
139
+ if (field_value.includes(operand)) return false;
140
+ } else if (field_value === operand) {
141
+ return false;
142
+ }
83
143
  break;
84
144
  case '$gt':
85
- if (field_value <= operand) return false;
145
+ if (Array.isArray(field_value)) {
146
+ let found = false;
147
+ for (let i = 0; i < field_value.length; i++) {
148
+ if (field_value[i] > operand) {
149
+ found = true;
150
+ break;
151
+ }
152
+ }
153
+ if (!found) return false;
154
+ } else if (field_value <= operand) {
155
+ return false;
156
+ }
86
157
  break;
87
158
  case '$gte':
88
- if (field_value < operand) return false;
159
+ if (Array.isArray(field_value)) {
160
+ let found = false;
161
+ for (let i = 0; i < field_value.length; i++) {
162
+ if (field_value[i] >= operand) {
163
+ found = true;
164
+ break;
165
+ }
166
+ }
167
+ if (!found) return false;
168
+ } else if (field_value < operand) {
169
+ return false;
170
+ }
89
171
  break;
90
172
  case '$lt':
91
- if (field_value >= operand) return false;
173
+ if (Array.isArray(field_value)) {
174
+ let found = false;
175
+ for (let i = 0; i < field_value.length; i++) {
176
+ if (field_value[i] < operand) {
177
+ found = true;
178
+ break;
179
+ }
180
+ }
181
+ if (!found) return false;
182
+ } else if (field_value >= operand) {
183
+ return false;
184
+ }
92
185
  break;
93
186
  case '$lte':
94
- if (field_value > operand) return false;
187
+ if (Array.isArray(field_value)) {
188
+ let found = false;
189
+ for (let i = 0; i < field_value.length; i++) {
190
+ if (field_value[i] <= operand) {
191
+ found = true;
192
+ break;
193
+ }
194
+ }
195
+ if (!found) return false;
196
+ } else if (field_value > operand) {
197
+ return false;
198
+ }
95
199
  break;
96
200
  case '$in':
97
- if (!Array.isArray(operand) || !operand.includes(field_value)) return false;
201
+ if (!Array.isArray(operand)) return false;
202
+ if (Array.isArray(field_value)) {
203
+ // Check if any array element is in the operand array
204
+ let found = false;
205
+ for (let i = 0; i < field_value.length; i++) {
206
+ if (operand.includes(field_value[i])) {
207
+ found = true;
208
+ break;
209
+ }
210
+ }
211
+ if (!found) return false;
212
+ } else if (!operand.includes(field_value)) {
213
+ return false;
214
+ }
98
215
  break;
99
216
  case '$nin':
100
- if (!Array.isArray(operand) || operand.includes(field_value)) return false;
217
+ if (!Array.isArray(operand)) return false;
218
+ if (Array.isArray(field_value)) {
219
+ // Check if any array element is in the operand array
220
+ for (let i = 0; i < field_value.length; i++) {
221
+ if (operand.includes(field_value[i])) {
222
+ return false;
223
+ }
224
+ }
225
+ } else if (operand.includes(field_value)) {
226
+ return false;
227
+ }
101
228
  break;
102
229
  case '$exists':
103
230
  const exists = field_exists(document, field);
@@ -108,7 +235,18 @@ const matches_filter = (document, filter) => {
108
235
  // Handle $options parameter for regex flags
109
236
  const regex_options = value.$options || '';
110
237
  const regex = new RegExp(operand, regex_options);
111
- if (!regex.test(field_value)) return false;
238
+ if (Array.isArray(field_value)) {
239
+ let found = false;
240
+ for (let i = 0; i < field_value.length; i++) {
241
+ if (typeof field_value[i] === 'string' && regex.test(field_value[i])) {
242
+ found = true;
243
+ break;
244
+ }
245
+ }
246
+ if (!found) return false;
247
+ } else if (!regex.test(field_value)) {
248
+ return false;
249
+ }
112
250
  break;
113
251
  case '$options':
114
252
  // $options is handled as part of $regex, skip it here
@@ -118,7 +256,12 @@ const matches_filter = (document, filter) => {
118
256
  }
119
257
  }
120
258
  } else {
121
- if (field_value !== value) return false;
259
+ // Direct field matching with array support
260
+ if (Array.isArray(field_value)) {
261
+ if (!field_value.includes(value)) return false;
262
+ } else if (field_value !== value) {
263
+ return false;
264
+ }
122
265
  }
123
266
  }
124
267
 
@@ -15,19 +15,48 @@ const { create_context_logger } = create_logger('find_one');
15
15
 
16
16
  /**
17
17
  * Gets the value of a nested field from a document using dot notation.
18
+ * Handles nested array object queries like 'reviews.rating' where reviews is an array of objects.
18
19
  * @param {Object} document - Document to extract field value from
19
- * @param {string} field_path - Dot-separated field path (e.g., 'user.name')
20
- * @returns {any} Field value or undefined if not found
20
+ * @param {string} field_path - Dot-separated field path (e.g., 'user.name', 'reviews.rating')
21
+ * @returns {any} Field value, array of values for nested array queries, or undefined if not found
21
22
  */
22
23
  const get_field_value = (document, field_path) => {
23
24
  const parts = field_path.split('.');
24
25
  let value = document;
25
26
 
26
- for (const part of parts) {
27
+ for (let i = 0; i < parts.length; i++) {
28
+ const part = parts[i];
29
+
27
30
  if (value === null || value === undefined) {
28
31
  return undefined;
29
32
  }
33
+
30
34
  value = value[part];
35
+
36
+ // If we got an array and there are more parts to traverse
37
+ if (Array.isArray(value) && i < parts.length - 1) {
38
+ // Extract the remaining path
39
+ const remaining_path = parts.slice(i + 1).join('.');
40
+
41
+ // Collect all matching values from array elements
42
+ const nested_values = [];
43
+ for (let j = 0; j < value.length; j++) {
44
+ const item = value[j];
45
+ if (typeof item === 'object' && item !== null) {
46
+ const nested_value = get_field_value(item, remaining_path);
47
+ if (nested_value !== undefined) {
48
+ if (Array.isArray(nested_value)) {
49
+ nested_values.push(...nested_value);
50
+ } else {
51
+ nested_values.push(nested_value);
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ // Return array of values for nested array queries
58
+ return nested_values.length > 0 ? nested_values : undefined;
59
+ }
31
60
  }
32
61
 
33
62
  return value;
@@ -60,6 +89,7 @@ const field_exists = (document, field_path) => {
60
89
 
61
90
  /**
62
91
  * Checks if a document matches the given filter criteria.
92
+ * Supports MongoDB-like query operators with optimized array field matching.
63
93
  * @param {Object} document - Document to test
64
94
  * @param {Object} filter - Filter criteria with MongoDB-style operators
65
95
  * @returns {boolean} True if document matches filter
@@ -70,6 +100,27 @@ const matches_filter = (document, filter) => {
70
100
  return true;
71
101
  }
72
102
 
103
+ // Handle $or operator at the top level
104
+ if (filter.$or && Array.isArray(filter.$or)) {
105
+ const or_conditions = filter.$or;
106
+ let or_match = false;
107
+ for (let i = 0; i < or_conditions.length; i++) {
108
+ if (matches_filter(document, or_conditions[i])) {
109
+ or_match = true;
110
+ break;
111
+ }
112
+ }
113
+ if (!or_match) return false;
114
+
115
+ // Continue checking other conditions (if any) outside of $or
116
+ const remaining_filter = { ...filter };
117
+ delete remaining_filter.$or;
118
+ if (Object.keys(remaining_filter).length > 0) {
119
+ return matches_filter(document, remaining_filter);
120
+ }
121
+ return true;
122
+ }
123
+
73
124
  for (const [field, value] of Object.entries(filter)) {
74
125
  const field_value = get_field_value(document, field);
75
126
 
@@ -77,28 +128,105 @@ const matches_filter = (document, filter) => {
77
128
  for (const [operator, operand] of Object.entries(value)) {
78
129
  switch (operator) {
79
130
  case '$eq':
80
- if (field_value !== operand) return false;
131
+ // Handle array field matching for $eq
132
+ if (Array.isArray(field_value)) {
133
+ if (!field_value.includes(operand)) return false;
134
+ } else if (field_value !== operand) {
135
+ return false;
136
+ }
81
137
  break;
82
138
  case '$ne':
83
- if (field_value === operand) return false;
139
+ // Handle array field matching for $ne
140
+ if (Array.isArray(field_value)) {
141
+ if (field_value.includes(operand)) return false;
142
+ } else if (field_value === operand) {
143
+ return false;
144
+ }
84
145
  break;
85
146
  case '$gt':
86
- if (field_value <= operand) return false;
147
+ if (Array.isArray(field_value)) {
148
+ let found = false;
149
+ for (let i = 0; i < field_value.length; i++) {
150
+ if (field_value[i] > operand) {
151
+ found = true;
152
+ break;
153
+ }
154
+ }
155
+ if (!found) return false;
156
+ } else if (field_value <= operand) {
157
+ return false;
158
+ }
87
159
  break;
88
160
  case '$gte':
89
- if (field_value < operand) return false;
161
+ if (Array.isArray(field_value)) {
162
+ let found = false;
163
+ for (let i = 0; i < field_value.length; i++) {
164
+ if (field_value[i] >= operand) {
165
+ found = true;
166
+ break;
167
+ }
168
+ }
169
+ if (!found) return false;
170
+ } else if (field_value < operand) {
171
+ return false;
172
+ }
90
173
  break;
91
174
  case '$lt':
92
- if (field_value >= operand) return false;
175
+ if (Array.isArray(field_value)) {
176
+ let found = false;
177
+ for (let i = 0; i < field_value.length; i++) {
178
+ if (field_value[i] < operand) {
179
+ found = true;
180
+ break;
181
+ }
182
+ }
183
+ if (!found) return false;
184
+ } else if (field_value >= operand) {
185
+ return false;
186
+ }
93
187
  break;
94
188
  case '$lte':
95
- if (field_value > operand) return false;
189
+ if (Array.isArray(field_value)) {
190
+ let found = false;
191
+ for (let i = 0; i < field_value.length; i++) {
192
+ if (field_value[i] <= operand) {
193
+ found = true;
194
+ break;
195
+ }
196
+ }
197
+ if (!found) return false;
198
+ } else if (field_value > operand) {
199
+ return false;
200
+ }
96
201
  break;
97
202
  case '$in':
98
- if (!Array.isArray(operand) || !operand.includes(field_value)) return false;
203
+ if (!Array.isArray(operand)) return false;
204
+ if (Array.isArray(field_value)) {
205
+ // Check if any array element is in the operand array
206
+ let found = false;
207
+ for (let i = 0; i < field_value.length; i++) {
208
+ if (operand.includes(field_value[i])) {
209
+ found = true;
210
+ break;
211
+ }
212
+ }
213
+ if (!found) return false;
214
+ } else if (!operand.includes(field_value)) {
215
+ return false;
216
+ }
99
217
  break;
100
218
  case '$nin':
101
- if (!Array.isArray(operand) || operand.includes(field_value)) return false;
219
+ if (!Array.isArray(operand)) return false;
220
+ if (Array.isArray(field_value)) {
221
+ // Check if any array element is in the operand array
222
+ for (let i = 0; i < field_value.length; i++) {
223
+ if (operand.includes(field_value[i])) {
224
+ return false;
225
+ }
226
+ }
227
+ } else if (operand.includes(field_value)) {
228
+ return false;
229
+ }
102
230
  break;
103
231
  case '$exists':
104
232
  const exists = field_exists(document, field);
@@ -109,7 +237,18 @@ const matches_filter = (document, filter) => {
109
237
  // Handle $options parameter for regex flags
110
238
  const regex_options = value.$options || '';
111
239
  const regex = new RegExp(operand, regex_options);
112
- if (!regex.test(field_value)) return false;
240
+ if (Array.isArray(field_value)) {
241
+ let found = false;
242
+ for (let i = 0; i < field_value.length; i++) {
243
+ if (typeof field_value[i] === 'string' && regex.test(field_value[i])) {
244
+ found = true;
245
+ break;
246
+ }
247
+ }
248
+ if (!found) return false;
249
+ } else if (!regex.test(field_value)) {
250
+ return false;
251
+ }
113
252
  break;
114
253
  case '$options':
115
254
  // $options is handled as part of $regex, skip it here
@@ -119,7 +258,12 @@ const matches_filter = (document, filter) => {
119
258
  }
120
259
  }
121
260
  } else {
122
- if (field_value !== value) return false;
261
+ // Direct field matching with array support
262
+ if (Array.isArray(field_value)) {
263
+ if (!field_value.includes(value)) return false;
264
+ } else if (field_value !== value) {
265
+ return false;
266
+ }
123
267
  }
124
268
  }
125
269