@cero-base/cero 0.8.8 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,15 @@ const recovered = await cero.restore(me, 'twelve words …')
85
85
  | `{ … }` (plain object) | a child handle — its own scope, share by invite |
86
86
  | `local: { … }` | device-only — never leaves this device |
87
87
 
88
+ A collection can declare **secondary indexes** for fast exact-field lookups:
89
+
90
+ ```js
91
+ cero.t.collection(
92
+ { text: cero.t.string },
93
+ { indexes: { 'by-text': ['text'] } } // name → field(s); query via `cero.get(ref, { text: '…' })`
94
+ )
95
+ ```
96
+
88
97
  ## Operators
89
98
 
90
99
  Every operator takes a `ref` (e.g. `me.profile`, `room.messages`) as the first arg.
@@ -110,19 +119,30 @@ await cero.set(room.messages, { id: 'abc', text: 'edited' }) // upsert by id
110
119
 
111
120
  ### `cero.get(ref, q?)`
112
121
 
113
- Read. Three shapes depending on `q`:
122
+ Read. The shape of `q` decides what comes back:
114
123
 
115
124
  ```js
116
125
  const { data } = await cero.get(me.profile) // single → the row (or null)
117
126
  const { data } = await cero.get(room.messages, 'abc') // by id → the row (or null)
127
+
128
+ // collection → paginated, with range filters
118
129
  const { data, total, size } = await cero.get(room.messages, {
119
- gt: 'm-2025', // filters: gt / gte / lt / lte
130
+ gt: 'm-2025', // range: gt / gte / lt / lte
120
131
  limit: 20,
121
132
  reverse: true
122
- }) // collection → paginated
133
+ })
134
+
135
+ // search → case-insensitive substring across string fields
136
+ const { data } = await cero.get(room.messages, { search: 'jo sm' })
137
+ // every space-separated term must match (AND); folds case + diacritics ('jose' ⇢ 'José').
138
+ // `fields` restricts which fields are searched (default: all string fields):
139
+ const { data } = await cero.get(room.guests, { search: 'jose', fields: ['name'] })
140
+
141
+ // exact field match → routed through a declared secondary index when the field has one
142
+ const { data } = await cero.get(room.messages, { text: 'alpha' })
123
143
  ```
124
144
 
125
- `total` is the unfiltered row count; `size` is what came back after `limit`.
145
+ `total` is the unfiltered row count; `size` is what came back after `limit`. `search` and exact-field filters compose with `limit` / `reverse`, and `cero.count(ref, q)` honors them too.
126
146
 
127
147
  ### `cero.del(ref, id)`
128
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cero-base/cero",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -78,7 +78,7 @@
78
78
  "test": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
79
79
  },
80
80
  "dependencies": {
81
- "@cero-base/core": "^0.8.8",
81
+ "@cero-base/core": "^0.8.10",
82
82
  "b4a": "^1.8.1",
83
83
  "bare-abort-controller": "^1.1.2",
84
84
  "bare-crypto": "^1.14.1",
@@ -120,7 +120,14 @@ function isPlainHandle(v) {
120
120
  }
121
121
 
122
122
  function compile(root, ns, scope = 'main') {
123
- const ctx = { types: [], collections: [], dispatches: [], meta: { ns, refs: {} }, ns }
123
+ const ctx = {
124
+ types: [],
125
+ collections: [],
126
+ dispatches: [],
127
+ indexes: [],
128
+ meta: { ns, refs: {} },
129
+ ns
130
+ }
124
131
 
125
132
  Object.assign(ctx.meta.refs, builtinRefs(ns, scope))
126
133
 
@@ -136,6 +143,7 @@ function compile(root, ns, scope = 'main') {
136
143
  types: ctx.types,
137
144
  collections: ctx.collections,
138
145
  dispatches: ctx.dispatches,
146
+ indexes: ctx.indexes,
139
147
  meta: ctx.meta
140
148
  }
141
149
  }
@@ -173,6 +181,12 @@ function register(name, node, ctx) {
173
181
  ctx.dispatches.push({ name: `set-${name}`, requestType: fqn })
174
182
  ctx.dispatches.push({ name: `del-${name}`, requestType: `@${ctx.ns}/del-by-id` })
175
183
  ctx.meta.refs[name] = { kind: 'collection', path: [name], schema: fqn }
184
+ if (node.indexes) {
185
+ for (const [idx, fields] of Object.entries(node.indexes)) {
186
+ ctx.indexes.push({ name: `${name}-${idx}`, collection: fqn, key: fields })
187
+ }
188
+ ctx.meta.refs[name].indexes = node.indexes
189
+ }
176
190
  }
177
191
  }
178
192
 
@@ -184,7 +198,7 @@ function fieldsFor(fields) {
184
198
  }))
185
199
  }
186
200
 
187
- function emitMain(dir, ns, { types, collections, dispatches }, { rpc, extend = {} }) {
201
+ function emitMain(dir, ns, { types, collections, dispatches, indexes = [] }, { rpc, extend = {} }) {
188
202
  const schemaDir = join(dir, 'schema')
189
203
  const dbDir = join(dir, 'db')
190
204
  const dispatchDir = join(dir, 'dispatch')
@@ -201,6 +215,7 @@ function emitMain(dir, ns, { types, collections, dispatches }, { rpc, extend = {
201
215
  const dns = db.namespace(ns)
202
216
  for (const desc of builtinCollections(ns, 'main')) dns.collections.register(desc)
203
217
  for (const desc of collections) dns.collections.register(desc)
218
+ for (const desc of indexes) dns.indexes.register(desc)
204
219
  HyperdbBuilder.toDisk(db, dbDir, { esm: true })
205
220
 
206
221
  const d = Hyperdispatch.from(schemaDir, dispatchDir)
@@ -217,7 +232,7 @@ function emitMain(dir, ns, { types, collections, dispatches }, { rpc, extend = {
217
232
  }
218
233
  }
219
234
 
220
- function emitLocal(dir, ns, { types, collections }) {
235
+ function emitLocal(dir, ns, { types, collections, indexes = [] }) {
221
236
  const schemaDir = join(dir, 'schema')
222
237
  const dbDir = join(dir, 'db')
223
238
 
@@ -231,6 +246,7 @@ function emitLocal(dir, ns, { types, collections }) {
231
246
  const dns = db.namespace(ns)
232
247
  for (const desc of builtinCollections(ns, 'local')) dns.collections.register(desc)
233
248
  for (const desc of collections) dns.collections.register(desc)
249
+ for (const desc of indexes) dns.indexes.register(desc)
234
250
  HyperdbBuilder.toDisk(db, dbDir, { esm: true })
235
251
  }
236
252