@omgbase/oqx 0.9.0 → 0.10.1

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/src/semantics.ts CHANGED
@@ -167,9 +167,51 @@ export function parseRangeString(s: string): OqxRange | null {
167
167
  return null;
168
168
  }
169
169
 
170
+ // ---- entries: the explicit record → collection bridge -----------------------
171
+ //
172
+ // A plain object is NOT iterable in OQX (`from ${obj}` is one row). `entries(x)`
173
+ // converts it explicitly into a collection of ENTRY values, each `{ key, value }`
174
+ // tagged (non-enumerably, so JSON/deepEqual see a plain record) so the engine
175
+ // can recognize one: when an entry becomes a query scope, the scope's ROW is
176
+ // the property's value (`$value`, bare names) and `$key` is scope metadata.
177
+ // Arrays yield numeric index keys; a Map yields its entries; null/scalars yield
178
+ // nothing. (Arrays never expose an implicit `$key` — `entries(arr)` is how you
179
+ // ask for the index.)
180
+
181
+ const ENTRY: unique symbol = Symbol.for("oqx.entry");
182
+
183
+ export interface OqxEntry {
184
+ readonly key: unknown;
185
+ readonly value: unknown;
186
+ readonly [ENTRY]: true;
187
+ }
188
+
189
+ /** Construct an entry value (see entriesOf). */
190
+ export function makeEntry(key: unknown, value: unknown): OqxEntry {
191
+ const e = { key, value } as { key: unknown; value: unknown; [ENTRY]?: true };
192
+ Object.defineProperty(e, ENTRY, { value: true, enumerable: false });
193
+ return e as OqxEntry;
194
+ }
195
+
196
+ /** Whether a value is an entry produced by `entries()` / `makeEntry`. */
197
+ export function isEntry(v: unknown): v is OqxEntry {
198
+ return typeof v === "object" && v !== null && (v as { [ENTRY]?: unknown })[ENTRY] === true;
199
+ }
200
+
201
+ /** The entries of a host value: object → own enumerable (key, value) pairs in
202
+ * insertion order; array → (index, element); Map → its entries; anything else
203
+ * (absent, scalars, ranges) → none. */
204
+ export function entriesOf(v: unknown): OqxEntry[] {
205
+ if (v == null || typeof v !== "object" || isRange(v)) return [];
206
+ if (Array.isArray(v)) return v.map((x, i) => makeEntry(i, x));
207
+ if (v instanceof Map) return Array.from(v, ([k, x]) => makeEntry(k, x));
208
+ return Object.entries(v as Record<string, unknown>).map(([k, x]) => makeEntry(k, x));
209
+ }
210
+
170
211
  /** Free functions callable as `name(args)`. */
171
212
  export const BUILTIN_FUNCTIONS: Record<string, (args: unknown[]) => unknown> = {
172
213
  list: (args) => toList(args[0]),
214
+ entries: (args) => entriesOf(args[0]),
173
215
  size: (args) => sizeOf(args[0]),
174
216
  has: (args) => args[0] != null,
175
217
  // Coerce a string to a range value (or pass a range through); anything else,