@vibeorm/runtime 2.0.0-alpha.4 → 2.0.0-alpha.5

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/dist/db-now.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  /** The database-clock sentinel produced by {@link dbNow}. */
19
19
  export type DbNow = {
20
20
  readonly __vibeDbNow: true;
21
- /** Whole seconds added to the database's now() (negative = past). */
21
+ /** Seconds added to the database's now() (fractional OK; negative = past). */
22
22
  readonly offsetSeconds: number;
23
23
  };
24
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"db-now.d.ts","sourceRoot":"","sources":["../src/db-now.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,6DAA6D;AAC7D,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAUxE;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAYtD"}
1
+ {"version":3,"file":"db-now.d.ts","sourceRoot":"","sources":["../src/db-now.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,6DAA6D;AAC7D,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAUxE;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAYtD"}
package/dist/index.js CHANGED
@@ -14,10 +14,10 @@ function toSqlExecutor(params) {
14
14
  import { VibeError } from "@vibeorm/schema";
15
15
  function dbNow(options) {
16
16
  const offset = options?.plusSeconds ?? 0;
17
- if (typeof offset !== "number" || !Number.isInteger(offset)) {
17
+ if (typeof offset !== "number" || !Number.isFinite(offset)) {
18
18
  throw new VibeError({
19
19
  code: "VIBE_VALIDATION",
20
- message: `dbNow plusSeconds must be a whole number of seconds, got ${String(offset)}`,
20
+ message: `dbNow plusSeconds must be a finite number of seconds (fractional is fine), got ${String(offset)}`,
21
21
  meta: { plusSeconds: offset }
22
22
  });
23
23
  }
@@ -167,26 +167,44 @@ function buildModelMeta(params) {
167
167
  primaryKey.push(...fields.filter((field) => field.isId));
168
168
  }
169
169
  const compoundUniques = new Map;
170
- const addCompound = (names) => {
171
- if (names.length < 2)
172
- return;
173
- const key = names.join("_");
174
- if (compoundUniques.has(key))
175
- return;
170
+ const singleIndexUniques = [];
171
+ const resolveFields = (names) => {
176
172
  const resolved = [];
177
173
  for (const name of names) {
178
174
  const field = fieldByName.get(name);
179
175
  if (field === undefined)
180
- return;
176
+ return null;
181
177
  resolved.push(field);
182
178
  }
183
- compoundUniques.set(key, resolved);
179
+ return resolved;
180
+ };
181
+ const addCompound = (names, nullsNotDistinct) => {
182
+ if (names.length < 2)
183
+ return;
184
+ const key = names.join("_");
185
+ if (compoundUniques.has(key))
186
+ return;
187
+ const resolved = resolveFields(names);
188
+ if (resolved === null)
189
+ return;
190
+ compoundUniques.set(key, { fields: resolved, nullsNotDistinct });
184
191
  };
185
192
  if (primaryKey.length > 1)
186
- addCompound(primaryKey.map((field) => field.name));
193
+ addCompound(primaryKey.map((field) => field.name), false);
187
194
  for (const index of model.indexes) {
188
- if (index.kind === "unique" && index.where === undefined) {
189
- addCompound(index.fields.map((entry) => entry.name));
195
+ if (index.kind !== "unique" || index.where !== undefined)
196
+ continue;
197
+ const names = index.fields.map((entry) => entry.name);
198
+ const nullsNotDistinct = index.nullsNotDistinct === true;
199
+ if (names.length === 1) {
200
+ const field = resolveFields(names)?.[0];
201
+ if (field === undefined || field.isId || field.isUnique)
202
+ continue;
203
+ if (singleIndexUniques.some((entry) => entry.fields[0]?.name === field.name))
204
+ continue;
205
+ singleIndexUniques.push({ fields: [field], nullsNotDistinct });
206
+ } else {
207
+ addCompound(names, nullsNotDistinct);
190
208
  }
191
209
  }
192
210
  const computedSpecs = params.computed;
@@ -204,6 +222,7 @@ function buildModelMeta(params) {
204
222
  primaryKey,
205
223
  uniqueFields: fields.filter((field) => field.isId || field.isUnique),
206
224
  compoundUniques,
225
+ singleIndexUniques,
207
226
  relations: model.relations,
208
227
  relationByName: new Map(model.relations.map((relation) => [relation.name, relation])),
209
228
  isView: model.isView,
@@ -2096,7 +2115,7 @@ function expandCompoundWhere(params) {
2096
2115
  for (const [key, value] of Object.entries(where)) {
2097
2116
  if (value === undefined)
2098
2117
  continue;
2099
- const fields = model.compoundUniques.get(key);
2118
+ const fields = model.compoundUniques.get(key)?.fields;
2100
2119
  if (fields === undefined)
2101
2120
  continue;
2102
2121
  const spec = requireObject({ value, what: `${what}.${key}`, model });
@@ -2115,33 +2134,52 @@ function expandCompoundWhere(params) {
2115
2134
  }
2116
2135
  return expanded ?? where;
2117
2136
  }
2137
+ function isAddressableMember(params) {
2138
+ return isDirectValue(params.value) || params.nullsNotDistinct && params.value === null;
2139
+ }
2118
2140
  function assertUniqueWhere(params) {
2119
2141
  const { model, where, method } = params;
2120
2142
  for (const field of model.uniqueFields) {
2121
2143
  if (isDirectValue(where[field.name]))
2122
2144
  return;
2123
2145
  }
2124
- for (const fields of model.compoundUniques.values()) {
2125
- if (fields.every((field) => isDirectValue(where[field.name])))
2146
+ for (const single of model.singleIndexUniques) {
2147
+ const field = single.fields[0];
2148
+ if (isAddressableMember({ value: where[field.name], nullsNotDistinct: single.nullsNotDistinct }))
2126
2149
  return;
2127
2150
  }
2128
- const candidates = [...model.uniqueFields.map((field) => field.name), ...model.compoundUniques.keys()];
2151
+ for (const group of model.compoundUniques.values()) {
2152
+ if (group.fields.every((field) => isAddressableMember({ value: where[field.name], nullsNotDistinct: group.nullsNotDistinct })))
2153
+ return;
2154
+ }
2155
+ const candidates = [
2156
+ ...model.uniqueFields.map((field) => field.name),
2157
+ ...model.singleIndexUniques.map((single) => single.fields[0].name),
2158
+ ...model.compoundUniques.keys()
2159
+ ];
2129
2160
  throw validationError2({
2130
2161
  message: `${model.clientName}.${method}() needs a unique where \u2014 one of: ${candidates.join(", ")}`,
2131
2162
  meta: { model: model.name, method, candidates }
2132
2163
  });
2133
2164
  }
2134
- function conflictTargetFields(params) {
2165
+ function conflictTargetGroup(params) {
2135
2166
  const { model, where } = params;
2136
2167
  for (const field of model.uniqueFields) {
2137
2168
  if (isDirectValue(where[field.name]))
2138
- return [field];
2169
+ return { fields: [field], nullsNotDistinct: false };
2170
+ }
2171
+ for (const single of model.singleIndexUniques) {
2172
+ const field = single.fields[0];
2173
+ if (isAddressableMember({ value: where[field.name], nullsNotDistinct: single.nullsNotDistinct })) {
2174
+ return single;
2175
+ }
2139
2176
  }
2140
- for (const fields of model.compoundUniques.values()) {
2141
- if (fields.every((field) => isDirectValue(where[field.name])))
2142
- return fields;
2177
+ for (const group of model.compoundUniques.values()) {
2178
+ if (group.fields.every((field) => isAddressableMember({ value: where[field.name], nullsNotDistinct: group.nullsNotDistinct }))) {
2179
+ return group;
2180
+ }
2143
2181
  }
2144
- return model.primaryKey;
2182
+ return { fields: model.primaryKey, nullsNotDistinct: false };
2145
2183
  }
2146
2184
  function sameDirectValue(a, b) {
2147
2185
  if (a === b)
@@ -2161,15 +2199,21 @@ function upsertUsesNativeStatement(params) {
2161
2199
  });
2162
2200
  assertUniqueWhere({ model, where, method: "upsert" });
2163
2201
  const create = requireObject({ value: args.create, what: "create", model });
2164
- const targets = conflictTargetFields({ model, where });
2165
- const targetNames = new Set(targets.map((field) => field.name));
2202
+ const target = conflictTargetGroup({ model, where });
2203
+ const targetNames = new Set(target.fields.map((field) => field.name));
2166
2204
  for (const [key, value] of Object.entries(where)) {
2167
2205
  if (value === undefined)
2168
2206
  continue;
2169
2207
  if (!targetNames.has(key))
2170
2208
  return false;
2171
2209
  }
2172
- return targets.every((field) => isDirectValue(create[field.name]) && sameDirectValue(create[field.name], where[field.name]));
2210
+ return target.fields.every((field) => {
2211
+ const createValue = create[field.name];
2212
+ const whereValue = where[field.name];
2213
+ if (target.nullsNotDistinct && createValue === null && whereValue === null)
2214
+ return true;
2215
+ return isDirectValue(createValue) && sameDirectValue(createValue, whereValue);
2216
+ });
2173
2217
  }
2174
2218
  var writePlanCache = new WeakMap;
2175
2219
  function getWritePlan(params) {
@@ -2451,7 +2495,8 @@ function parseCursor(params) {
2451
2495
  }
2452
2496
  if (fields.length === 1) {
2453
2497
  const field = fields[0];
2454
- if (!field.isId && !field.isUnique) {
2498
+ const isSingleIndexUnique = model.singleIndexUniques.some((single) => single.fields[0].name === field.name);
2499
+ if (!field.isId && !field.isUnique && !isSingleIndexUnique) {
2455
2500
  throw validationError2({
2456
2501
  message: `cursor field ${model.name}.${field.name} must be @id or @unique \u2014 a non-unique cursor cannot identify a position`,
2457
2502
  meta: { model: model.name, field: field.name }
@@ -2460,7 +2505,7 @@ function parseCursor(params) {
2460
2505
  } else {
2461
2506
  const names = new Set(fields.map((field) => field.name));
2462
2507
  const matchesGroup = (group) => group.length === names.size && group.every((field) => names.has(field.name));
2463
- const matches = matchesGroup(model.primaryKey) || [...model.compoundUniques.values()].some(matchesGroup);
2508
+ const matches = matchesGroup(model.primaryKey) || [...model.compoundUniques.values()].some((group) => matchesGroup(group.fields));
2464
2509
  if (!matches) {
2465
2510
  const candidates = [
2466
2511
  model.primaryKey.map((field) => field.name).join(" + ") || "none",
@@ -3662,7 +3707,7 @@ function buildQuery(params) {
3662
3707
  assertUniqueWhere({ model, where, method });
3663
3708
  const row = compileCreateRow({ dialect, model, data: args.create });
3664
3709
  const columns = orderedColumns({ model, values: row.values });
3665
- const targetFields = conflictTargetFields({ model, where });
3710
+ const targetFields = conflictTargetGroup({ model, where }).fields;
3666
3711
  for (const field of targetFields) {
3667
3712
  if (!(field.columnName in row.values)) {
3668
3713
  throw new VibeError5({
@@ -6984,4 +7029,4 @@ export {
6984
7029
  COUNT_ALIAS
6985
7030
  };
6986
7031
 
6987
- //# debugId=DDDCD7D2210268EA64756E2164756E21
7032
+ //# debugId=0C003BB2F334C66D64756E2164756E21