@joystick.js/db-canary 0.0.0-canary.2250 → 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 +144 -15
  5. package/src/server/lib/operations/find_one.js +144 -14
  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 q}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,r)=>{const o=r.split(".");let t=n;for(const e of o){if(t==null)return;t=t[e]}return t},P=(n,r)=>{const o=r.split(".");let t=n;for(let e=0;e<o.length;e++){if(t==null||typeof t!="object")return!1;if(e===o.length-1)return t.hasOwnProperty(o[e]);t=t[o[e]]}return!1},b=(n,r)=>{if(!r||Object.keys(r).length===0)return!0;if(r.$or&&Array.isArray(r.$or)){if(!r.$or.some(e=>b(n,e)))return!1;const t={...r};return delete t.$or,Object.keys(t).length>0?b(n,t):!0}for(const[o,t]of Object.entries(r)){const e=C(n,o);if(typeof t=="object"&&t!==null&&!Array.isArray(t))for(const[i,s]of Object.entries(t))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=t.$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!==t)return!1}return!0},U=(n,r)=>{if(!r||Object.keys(r).length===0)return n;const o=Object.values(r).some(e=>e===1||e===!0),t={};if(o){t._id=n._id;for(const[e,i]of Object.entries(r))e==="_id"&&(i===0||i===!1)?delete t._id:(i===1||i===!0)&&(t[e]=n[e])}else{Object.assign(t,n);for(const[e,i]of Object.entries(r))(i===0||i===!1)&&delete t[e]}return t},z=(n,r)=>!r||Object.keys(r).length===0?n:n.sort((o,t)=>{for(const[e,i]of Object.entries(r)){const s=o[e],c=t[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,r,o={},t={})=>{const e=R();if(!n)throw new Error("Database name is required");if(!r)throw new Error("Collection name is required");const i=D(),{projection:s,sort:c,limit:u,skip:k=0}=t,E=Date.now();try{let d=[],_=!1,$=null;const O=J(n,r,o);if(O){const{field:f,operators:h}=O,a=o[f];if($=f,typeof a=="object"&&a!==null&&!Array.isArray(a)){for(const l of h)if(a[l]!==void 0){const p=v(n,r,f,l,a[l]);if(p){_=!0,F(n,r,f);for(const m of p){const x=q(n,r,m),y=i.get(x);if(y){const A=JSON.parse(y);b(A,o)&&d.push(A)}}break}}}else if(h.includes("eq")){const l=v(n,r,f,"eq",a);if(l){_=!0,F(n,r,f);for(const p of l){const m=q(n,r,p),x=i.get(m);if(x){const y=JSON.parse(x);b(y,o)&&d.push(y)}}}}}if(!_){const f=`${n}:${r}:`,h=i.getRange({start:f,end:f+"\xFF"});for(const{key:a,value:l}of h){const p=JSON.parse(l);b(p,o)&&d.push(p)}}let g=z(d,c);k>0&&(g=g.slice(k)),u&&u>0&&(g=g.slice(0,u));const j=g.map(f=>U(f,s)),w=Date.now()-E;try{N(r,o,w,_,$)}catch(f){e.warn("Failed to record query for auto-indexing",{error:f.message})}return e.info("Find operation completed",{database:n,collection:r,documents_found:j.length,total_matching:d.length,used_index:_,indexed_field:$,execution_time_ms:w}),j}catch(d){throw e.error("Failed to find documents",{database:n,collection:r,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 q,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 A}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},p=(o,t)=>{if(!t||Object.keys(t).length===0)return!0;if(t.$or&&Array.isArray(t.$or)){if(!t.$or.some(e=>p(o,e)))return!1;const r={...t};return delete r.$or,Object.keys(r).length>0?p(o,r):!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 k=r.$options||"";if(!new RegExp(s,k).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=q(),{projection:s,sort:x}=r,k=Date.now();try{let f=null,a=!1,g=null;const O=v(o,t,n);if(O){const{field:c,operators:y}=O,d=n[c];if(g=c,typeof d=="object"&&d!==null&&!Array.isArray(d)){for(const u of y)if(d[u]!==void 0){const l=m(o,t,c,u,d[u]);if(l&&l.length>0){a=!0,A(o,t,c);for(const $ of l){const b=j(o,t,$),_=i.get(b);if(_)try{const w=JSON.parse(_);if(p(w,n)){f=w;break}}catch{continue}}break}}}else if(y.includes("eq")){const u=m(o,t,c,"eq",d);if(u&&u.length>0){a=!0,A(o,t,c);for(const l of u){const $=j(o,t,l),b=i.get($);if(b)try{const _=JSON.parse(b);if(p(_,n)){f=_;break}}catch{continue}}}}}if(!a){const c=`${o}:${t}:`,y=i.getRange({start:c,end:c+"\xFF"});for(const{key:d,value:u}of y)try{const l=JSON.parse(u);if(p(l,n)){f=l;break}}catch{continue}}const h=Date.now()-k;try{D(t,n,h,a,g)}catch(c){e.warn("Failed to record query for auto-indexing",{error:c.message})}if(f){const c=S(f,s);return e.info("Document found",{database:o,collection:t,document_id:f._id,used_index:a,indexed_field:g,execution_time_ms:h}),c}return e.info("No document found",{database:o,collection:t,used_index:a,indexed_field:g,execution_time_ms:h}),null}catch(f){throw e.error("Failed to find document",{database:o,collection:t,error:f.message}),f}};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.2250",
5
- "canary_version": "0.0.0-canary.2249",
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
@@ -71,7 +100,14 @@ const matches_filter = (document, filter) => {
71
100
 
72
101
  // Handle $or operator at the top level
73
102
  if (filter.$or && Array.isArray(filter.$or)) {
74
- const or_match = filter.$or.some(or_condition => matches_filter(document, or_condition));
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
+ }
75
111
  if (!or_match) return false;
76
112
 
77
113
  // Continue checking other conditions (if any) outside of $or
@@ -90,28 +126,105 @@ const matches_filter = (document, filter) => {
90
126
  for (const [operator, operand] of Object.entries(value)) {
91
127
  switch (operator) {
92
128
  case '$eq':
93
- 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
+ }
94
135
  break;
95
136
  case '$ne':
96
- 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
+ }
97
143
  break;
98
144
  case '$gt':
99
- 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
+ }
100
157
  break;
101
158
  case '$gte':
102
- 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
+ }
103
171
  break;
104
172
  case '$lt':
105
- 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
+ }
106
185
  break;
107
186
  case '$lte':
108
- 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
+ }
109
199
  break;
110
200
  case '$in':
111
- 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
+ }
112
215
  break;
113
216
  case '$nin':
114
- 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
+ }
115
228
  break;
116
229
  case '$exists':
117
230
  const exists = field_exists(document, field);
@@ -122,7 +235,18 @@ const matches_filter = (document, filter) => {
122
235
  // Handle $options parameter for regex flags
123
236
  const regex_options = value.$options || '';
124
237
  const regex = new RegExp(operand, regex_options);
125
- 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
+ }
126
250
  break;
127
251
  case '$options':
128
252
  // $options is handled as part of $regex, skip it here
@@ -132,7 +256,12 @@ const matches_filter = (document, filter) => {
132
256
  }
133
257
  }
134
258
  } else {
135
- 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
+ }
136
265
  }
137
266
  }
138
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
@@ -72,7 +102,14 @@ const matches_filter = (document, filter) => {
72
102
 
73
103
  // Handle $or operator at the top level
74
104
  if (filter.$or && Array.isArray(filter.$or)) {
75
- const or_match = filter.$or.some(or_condition => matches_filter(document, or_condition));
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
+ }
76
113
  if (!or_match) return false;
77
114
 
78
115
  // Continue checking other conditions (if any) outside of $or
@@ -91,28 +128,105 @@ const matches_filter = (document, filter) => {
91
128
  for (const [operator, operand] of Object.entries(value)) {
92
129
  switch (operator) {
93
130
  case '$eq':
94
- 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
+ }
95
137
  break;
96
138
  case '$ne':
97
- 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
+ }
98
145
  break;
99
146
  case '$gt':
100
- 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
+ }
101
159
  break;
102
160
  case '$gte':
103
- 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
+ }
104
173
  break;
105
174
  case '$lt':
106
- 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
+ }
107
187
  break;
108
188
  case '$lte':
109
- 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
+ }
110
201
  break;
111
202
  case '$in':
112
- 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
+ }
113
217
  break;
114
218
  case '$nin':
115
- 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
+ }
116
230
  break;
117
231
  case '$exists':
118
232
  const exists = field_exists(document, field);
@@ -123,7 +237,18 @@ const matches_filter = (document, filter) => {
123
237
  // Handle $options parameter for regex flags
124
238
  const regex_options = value.$options || '';
125
239
  const regex = new RegExp(operand, regex_options);
126
- 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
+ }
127
252
  break;
128
253
  case '$options':
129
254
  // $options is handled as part of $regex, skip it here
@@ -133,7 +258,12 @@ const matches_filter = (document, filter) => {
133
258
  }
134
259
  }
135
260
  } else {
136
- 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
+ }
137
267
  }
138
268
  }
139
269