@stndrds/schema 0.1.0-alpha.31 → 0.1.0-alpha.33

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.
@@ -1512,6 +1512,23 @@ function createMockUserProfilesRepository(stores) {
1512
1512
  stores.userProfiles.set(id, profile);
1513
1513
  }
1514
1514
  return Promise.resolve();
1515
+ },
1516
+ invite(data) {
1517
+ const tenantId = getTenantId();
1518
+ const profile = {
1519
+ id: generateId(),
1520
+ tenantId,
1521
+ authId: `invited-${generateId()}`,
1522
+ email: data.email,
1523
+ firstName: data.firstName,
1524
+ lastName: data.lastName,
1525
+ role: _nullishCoalesce(data.role, () => ( "member")),
1526
+ status: "pending",
1527
+ createdAt: /* @__PURE__ */ new Date(),
1528
+ updatedAt: /* @__PURE__ */ new Date()
1529
+ };
1530
+ stores.userProfiles.set(profile.id, profile);
1531
+ return Promise.resolve(profile);
1515
1532
  }
1516
1533
  };
1517
1534
  }
@@ -5151,11 +5168,10 @@ var GroupBuilder = class {
5151
5168
  return this.data;
5152
5169
  }
5153
5170
  };
5154
- var TableTabConfig = class {
5155
- /** @internal */
5156
- constructor(view2, base, relation2) {
5171
+ var BaseTableTabConfig = class {
5172
+ constructor(view2, tabData) {
5157
5173
  this.view = view2;
5158
- this.tabData = { ...base, type: "table", relation: relation2, columns: [] };
5174
+ this.tabData = tabData;
5159
5175
  }
5160
5176
  /**
5161
5177
  * Set columns to display
@@ -5203,11 +5219,22 @@ var TableTabConfig = class {
5203
5219
  return this;
5204
5220
  }
5205
5221
  /**
5206
- * Set default sort
5222
+ * Set default sort rules
5223
+ * @example .sorts([{ attribute: "lastName", direction: "asc" }])
5224
+ */
5225
+ sorts(value) {
5226
+ this.tabData.sorts = value;
5227
+ return this;
5228
+ }
5229
+ /**
5230
+ * Add a single sort rule (convenience method)
5207
5231
  * @example .sort("lastName") or .sort("createdAt", "desc")
5208
5232
  */
5209
- sort(field, order = "asc") {
5210
- this.tabData.sort = { field, order };
5233
+ sort(attribute, direction = "asc") {
5234
+ if (!this.tabData.sorts) {
5235
+ this.tabData.sorts = [];
5236
+ }
5237
+ this.tabData.sorts.push({ attribute, direction });
5211
5238
  return this;
5212
5239
  }
5213
5240
  /**
@@ -5238,6 +5265,31 @@ var TableTabConfig = class {
5238
5265
  this.view._addTab(this.tabData);
5239
5266
  }
5240
5267
  };
5268
+ var DirectTableTabConfig = class extends BaseTableTabConfig {
5269
+ /** @internal */
5270
+ constructor(view2, base, relationAttribute) {
5271
+ super(view2, {
5272
+ ...base,
5273
+ type: "table",
5274
+ relationMode: "direct",
5275
+ relationAttribute,
5276
+ columns: []
5277
+ });
5278
+ }
5279
+ };
5280
+ var InverseTableTabConfig = class extends BaseTableTabConfig {
5281
+ /** @internal */
5282
+ constructor(view2, base, sourceObject, relationAttribute) {
5283
+ super(view2, {
5284
+ ...base,
5285
+ type: "table",
5286
+ relationMode: "inverse",
5287
+ sourceObject,
5288
+ relationAttribute,
5289
+ columns: []
5290
+ });
5291
+ }
5292
+ };
5241
5293
  var CustomTabConfig = class {
5242
5294
  /** @internal */
5243
5295
  constructor(view2, base, component) {
@@ -5402,11 +5454,27 @@ var TabBuilder = class {
5402
5454
  return this.view._addTab(tab);
5403
5455
  }
5404
5456
  /**
5405
- * Create a table tab for a relation
5406
- * @example .table("contacts").columns("name", "email").crud()
5457
+ * Create a direct table tab for a relation attribute on the current object
5458
+ *
5459
+ * Use this when the current object has a relation attribute pointing to another object.
5460
+ *
5461
+ * @param relationAttribute - Name of the relation attribute on the current object
5462
+ * @example .table("members").columns("name", "email").crud() // Show users from Project.members
5463
+ */
5464
+ table(relationAttribute) {
5465
+ return new DirectTableTabConfig(this.view, this.base, relationAttribute);
5466
+ }
5467
+ /**
5468
+ * Create an inverse table tab showing records from another object that have a relation to us
5469
+ *
5470
+ * Use this when another object has a relation attribute pointing to the current object.
5471
+ *
5472
+ * @param sourceObject - Name of the object that has the relation to us
5473
+ * @param relationAttribute - Name of the relation attribute on the source object
5474
+ * @example .tableFrom("contacts", "company").columns("firstName", "lastName") // Show contacts where Contact.company = this
5407
5475
  */
5408
- table(relation2) {
5409
- return new TableTabConfig(this.view, this.base, relation2);
5476
+ tableFrom(sourceObject, relationAttribute) {
5477
+ return new InverseTableTabConfig(this.view, this.base, sourceObject, relationAttribute);
5410
5478
  }
5411
5479
  /**
5412
5480
  * Create a custom tab with a component
@@ -8444,6 +8512,12 @@ var RecordService = class extends TenantAwareService {
8444
8512
  if (!_optionalChain([options, 'optionalAccess', _220 => _220.skipHooks])) {
8445
8513
  await this.hookRegistry.execute("beforeUpdate", schema.name, hookCtx);
8446
8514
  }
8515
+ const hookModifiedValues = {};
8516
+ for (const key of Object.keys(hookCtx.newValues)) {
8517
+ if (!(key in data) && hookCtx.newValues[key] !== existing.values[key]) {
8518
+ hookModifiedValues[key] = hookCtx.newValues[key];
8519
+ }
8520
+ }
8447
8521
  if (_optionalChain([options, 'optionalAccess', _221 => _221.validate]) !== false) {
8448
8522
  if (_optionalChain([options, 'optionalAccess', _222 => _222.partial])) {
8449
8523
  validateDraftOrThrow(schema, mergedData);
@@ -8451,16 +8525,23 @@ var RecordService = class extends TenantAwareService {
8451
8525
  validateObjectOrThrow(schema, mergedData);
8452
8526
  }
8453
8527
  if (!_optionalChain([options, 'optionalAccess', _223 => _223.skipRelationValidation])) {
8454
- await this.relationService.validateRelationsOrThrow(schema, data);
8528
+ await this.relationService.validateRelationsOrThrow(schema, {
8529
+ ...data,
8530
+ ...hookModifiedValues
8531
+ });
8455
8532
  }
8456
8533
  if (!_optionalChain([options, 'optionalAccess', _224 => _224.skipUserValidation])) {
8457
- await this.userService.validateUsersOrThrow(schema, data);
8534
+ await this.userService.validateUsersOrThrow(schema, {
8535
+ ...data,
8536
+ ...hookModifiedValues
8537
+ });
8458
8538
  }
8459
8539
  }
8460
8540
  const completionStatus = computeRecordStatus(schema, mergedData);
8461
8541
  const label = await this.computeLabel(schema, mergedData);
8462
8542
  const updatePayload = {
8463
8543
  ...data,
8544
+ ...hookModifiedValues,
8464
8545
  __completionStatus: completionStatus,
8465
8546
  __label: label,
8466
8547
  __lastUpdatedBy: this.userId
@@ -8482,11 +8563,15 @@ var RecordService = class extends TenantAwareService {
8482
8563
  await this.hookRegistry.execute("afterUpdate", schema.name, afterCtx);
8483
8564
  }
8484
8565
  await this.recalculateParentRollups(updated, schema);
8485
- if (this.auditService && this.userId && changedAttributes.length > 0) {
8486
- const changes = changedAttributes.map((attr) => ({
8566
+ const allChangedAttributes = [
8567
+ ...changedAttributes,
8568
+ ...Object.keys(hookModifiedValues).filter((k) => !changedAttributes.includes(k))
8569
+ ];
8570
+ if (this.auditService && this.userId && allChangedAttributes.length > 0) {
8571
+ const changes = allChangedAttributes.map((attr) => ({
8487
8572
  field: attr,
8488
8573
  oldValue: _optionalChain([hookCtx, 'access', _227 => _227.oldValues, 'optionalAccess', _228 => _228[attr]]),
8489
- newValue: data[attr]
8574
+ newValue: hookCtx.newValues[attr]
8490
8575
  }));
8491
8576
  await this.auditService.logRecordAction({
8492
8577
  action: "record.updated",
@@ -9339,6 +9424,48 @@ var UserProfileService = class extends TenantAwareService {
9339
9424
  async isAdmin(profileId) {
9340
9425
  return await this.hasRole(profileId, "admin");
9341
9426
  }
9427
+ /**
9428
+ * Invite a new user by email.
9429
+ *
9430
+ * This method:
9431
+ * 1. Sends an invitation email via the auth provider (e.g., Supabase Auth)
9432
+ * 2. Creates a user profile with status "pending"
9433
+ * 3. Returns the created profile
9434
+ *
9435
+ * The user will receive an email with a link to accept the invitation.
9436
+ * When they click the link, their auth account is activated.
9437
+ *
9438
+ * @param data - Invitation data
9439
+ * @returns Created pending user profile
9440
+ *
9441
+ * @example
9442
+ * ```typescript
9443
+ * const profile = await service.inviteUser({
9444
+ * email: "john@example.com",
9445
+ * firstName: "John",
9446
+ * lastName: "Doe",
9447
+ * role: "member",
9448
+ * redirectTo: "https://app.example.com/welcome",
9449
+ * });
9450
+ * // Email sent automatically, profile.status === "pending"
9451
+ * ```
9452
+ */
9453
+ async inviteUser(data) {
9454
+ const existingEmail = await this.adapter.userProfiles.findByEmail(data.email);
9455
+ if (existingEmail) {
9456
+ throw new Error(`User with email "${data.email}" already exists in this tenant`);
9457
+ }
9458
+ const profile = await this.adapter.userProfiles.invite(data);
9459
+ if (this.auditService && this.userId) {
9460
+ await this.auditService.logUserAction({
9461
+ action: "user.invited",
9462
+ actorId: this.userId,
9463
+ targetUserId: profile.id,
9464
+ targetUserEmail: profile.email
9465
+ });
9466
+ }
9467
+ return profile;
9468
+ }
9342
9469
  };
9343
9470
 
9344
9471
  // src/runtime/services/view.service.ts
@@ -10086,4 +10213,5 @@ var NoopGeocodingAdapter = class {
10086
10213
 
10087
10214
 
10088
10215
 
10089
- exports.RELATION_TARGET_ANY = RELATION_TARGET_ANY; exports.isUniversalRelation = isUniversalRelation; exports.RecordReferencedError = RecordReferencedError; exports.AttributeInUseError = AttributeInUseError; exports.ObjectReferencedError = ObjectReferencedError; exports.NoopGeocodingAdapter = NoopGeocodingAdapter; exports.SYSTEM_FIELD_NAMES = SYSTEM_FIELD_NAMES; exports.RESERVED_ATTRIBUTE_NAMES = RESERVED_ATTRIBUTE_NAMES; exports.PolicyViolationError = PolicyViolationError; exports.SYSTEM_ATTRIBUTES = SYSTEM_ATTRIBUTES; exports.getSystemAttributeList = getSystemAttributeList; exports.isSystemAttribute = isSystemAttribute; exports.isSystemAttributeObject = isSystemAttributeObject; exports.asTenantId = asTenantId; exports.asUserId = asUserId; exports.generateId = generateId; exports.generatePrefixedId = generatePrefixedId; exports.EMPTY_VALUE_PLACEHOLDER = EMPTY_VALUE_PLACEHOLDER; exports.formatAttributeValue = formatAttributeValue; exports.SchemaErrorCode = SchemaErrorCode; exports.SchemaError = SchemaError; exports.NotFoundError = NotFoundError; exports.ObjectNotFoundError = ObjectNotFoundError; exports.AttributeNotFoundError = AttributeNotFoundError; exports.RecordNotFoundError = RecordNotFoundError; exports.UserProfileNotFoundError = UserProfileNotFoundError; exports.FileNotFoundError = FileNotFoundError; exports.ValidationError = ValidationError; exports.ProtectedResourceError = ProtectedResourceError; exports.SyncError = SyncError; exports.NotSystemObjectError = NotSystemObjectError; exports.DuplicateError = DuplicateError; exports.isSchemaError = isSchemaError; exports.isNotFoundError = isNotFoundError; exports.isValidationError = isValidationError; exports.isProtectedResourceError = isProtectedResourceError; exports.ForbiddenError = ForbiddenError; exports.ProtectedRoleError = ProtectedRoleError; exports.RoleNotFoundError = RoleNotFoundError; exports.isForbiddenError = isForbiddenError; exports.text = text; exports.textarea = textarea; exports.richtext = richtext; exports.number = number; exports.checkbox = checkbox; exports.date = date; exports.phone = phone; exports.currency = currency; exports.status = status; exports.select = select; exports.multiselect = multiselect; exports.location = location; exports.file = file; exports.user = user; exports.relation = relation; exports.rating = rating; exports.formula = formula; exports.rollup = rollup; exports.FlowRowBuilder = FlowRowBuilder; exports.FlowPageBuilder = FlowPageBuilder; exports.FlowBuilder = FlowBuilder; exports.flow = flow; exports.ObjectBuilder = ObjectBuilder; exports.object = object; exports.GroupBuilder = GroupBuilder; exports.TableTabConfig = TableTabConfig; exports.CustomTabConfig = CustomTabConfig; exports.NotesTabConfig = NotesTabConfig; exports.ActivityTabConfig = ActivityTabConfig; exports.TabBuilder = TabBuilder; exports.ViewBuilder = ViewBuilder; exports.view = view; exports.group = group; exports.registry = registry; exports.textConfigSchema = textConfigSchema; exports.textareaConfigSchema = textareaConfigSchema; exports.richtextConfigSchema = richtextConfigSchema; exports.numberConfigSchema = numberConfigSchema; exports.checkboxConfigSchema = checkboxConfigSchema; exports.dateConfigSchema = dateConfigSchema; exports.phoneConfigSchema = phoneConfigSchema; exports.currencyConfigSchema = currencyConfigSchema; exports.statusConfigSchema = statusConfigSchema; exports.locationConfigSchema = locationConfigSchema; exports.selectConfigSchema = selectConfigSchema; exports.multiselectConfigSchema = multiselectConfigSchema; exports.fileConfigSchema = fileConfigSchema; exports.userConfigSchema = userConfigSchema; exports.relationConfigSchema = relationConfigSchema; exports.ratingConfigSchema = ratingConfigSchema; exports.formulaConfigSchema = formulaConfigSchema; exports.rollupConfigSchema = rollupConfigSchema; exports.attributeConfigSchemas = attributeConfigSchemas; exports.getAttributeConfigSchema = getAttributeConfigSchema; exports.validateAttributeConfig = validateAttributeConfig; exports.parseAttributeConfig = parseAttributeConfig; exports.safeParseAttributeConfig = safeParseAttributeConfig; exports.createTextValidator = createTextValidator; exports.createNumberValidator = createNumberValidator; exports.createCheckboxValidator = createCheckboxValidator; exports.createDateValidator = createDateValidator; exports.createPhoneValidator = createPhoneValidator; exports.createCurrencyValidator = createCurrencyValidator; exports.createStatusValidator = createStatusValidator; exports.createSelectValidator = createSelectValidator; exports.createMultiselectValidator = createMultiselectValidator; exports.createLocationValidator = createLocationValidator; exports.createFileValidator = createFileValidator; exports.createUserValidator = createUserValidator; exports.createSingleRelationValidator = createSingleRelationValidator; exports.createMultiRelationValidator = createMultiRelationValidator; exports.createRelationValidator = createRelationValidator; exports.createRatingValidator = createRatingValidator; exports.createFormulaValidator = createFormulaValidator; exports.createRollupValidator = createRollupValidator; exports.createTextAreaValidator = createTextAreaValidator; exports.createRichtextValidator = createRichtextValidator; exports.createAttributeValidator = createAttributeValidator; exports.createObjectValidator = createObjectValidator; exports.validateAttribute = validateAttribute; exports.validateObject = validateObject; exports.validateObjectOrThrow = validateObjectOrThrow; exports.createDraftValidator = createDraftValidator; exports.validateDraft = validateDraft; exports.validateDraftOrThrow = validateDraftOrThrow; exports.getMissingRequiredAttributes = getMissingRequiredAttributes; exports.isRecordComplete = isRecordComplete; exports.computeRecordStatus = computeRecordStatus; exports.formatRecord = formatRecord; exports.formatRecords = formatRecords; exports.createDefaultState = createDefaultState; exports.SHORTCUT_TO_FILTER_OPERATOR = SHORTCUT_TO_FILTER_OPERATOR; exports.QueryNoResultError = QueryNoResultError; exports.QueryMultipleResultsError = QueryMultipleResultsError; exports.TenantContextError = TenantContextError; exports.getContext = getContext; exports.getTenantId = getTenantId; exports.getUserId = getUserId; exports.hasContext = hasContext; exports.runWithContext = runWithContext; exports.withTenantContext = withTenantContext; exports.QueryBuilder = QueryBuilder; exports.createQueryBuilder = createQueryBuilder; exports.evaluateFormula = evaluateFormula; exports.evaluateFormulaWithResult = evaluateFormulaWithResult; exports.formatFormulaResult = formatFormulaResult; exports.evaluateFormulaAttribute = evaluateFormulaAttribute; exports.validateFormulaExpression = validateFormulaExpression; exports.extractFormulaVariables = extractFormulaVariables; exports.extractRelationReferences = extractRelationReferences; exports.extractRelationNames = extractRelationNames; exports.hasRelationReferences = hasRelationReferences; exports.flattenRelationsForEval = flattenRelationsForEval; exports.evaluateFormulaWithRelations = evaluateFormulaWithRelations; exports.evaluateFormulaAttributeWithRelations = evaluateFormulaAttributeWithRelations; exports.InvalidPathError = InvalidPathError; exports.MaxDepthExceededError = MaxDepthExceededError; exports.parsePath = parsePath; exports.validatePath = validatePath; exports.pathHasManyCardinality = pathHasManyCardinality; exports.getPathDepth = getPathDepth; exports.getTargetAttributeName = getTargetAttributeName; exports.getRelationPath = getRelationPath; exports.traversePath = traversePath; exports.resolveSingleValue = resolveSingleValue; exports.resolveMultiplePaths = resolveMultiplePaths; exports.NoopHookRegistry = NoopHookRegistry; exports.DEFAULT_LABEL_FALLBACK = DEFAULT_LABEL_FALLBACK; exports.renderLabelExpression = renderLabelExpression; exports.isLabelExpression = isLabelExpression; exports.extractAttributeNames = extractAttributeNames; exports.enrichValuesWithSelectLabels = enrichValuesWithSelectLabels; exports.createMockAdapter = createMockAdapter; exports.PolicyRegistry = PolicyRegistry; exports.defaultPolicyRegistry = defaultPolicyRegistry; exports.notesPolicy = notesPolicy; exports.buildAuditChanges = buildAuditChanges; exports.TenantAwareService = TenantAwareService; exports.TenantAwareRepository = TenantAwareRepository; exports.AuditService = AuditService; exports.FileService = FileService; exports.FlowService = FlowService; exports.GeocodingService = GeocodingService; exports.GlobalSearchService = GlobalSearchService; exports.ObjectSchemaService = ObjectSchemaService; exports.PermissionService = PermissionService; exports.RelationService = RelationService; exports.RollupService = RollupService; exports.UserService = UserService; exports.RecordService = RecordService; exports.RelationResolverService = RelationResolverService; exports.RollupScheduler = RollupScheduler; exports.UserProfileService = UserProfileService; exports.ViewService = ViewService; exports.syncNativeViews = syncNativeViews; exports.verifyNativeViewsSync = verifyNativeViewsSync; exports.getViewSyncPreview = getViewSyncPreview; exports.syncNativeObjects = syncNativeObjects; exports.verifyNativeObjectsSync = verifyNativeObjectsSync; exports.getSyncPreview = getSyncPreview; exports.syncAll = syncAll;
10216
+
10217
+ exports.RELATION_TARGET_ANY = RELATION_TARGET_ANY; exports.isUniversalRelation = isUniversalRelation; exports.RecordReferencedError = RecordReferencedError; exports.AttributeInUseError = AttributeInUseError; exports.ObjectReferencedError = ObjectReferencedError; exports.NoopGeocodingAdapter = NoopGeocodingAdapter; exports.SYSTEM_FIELD_NAMES = SYSTEM_FIELD_NAMES; exports.RESERVED_ATTRIBUTE_NAMES = RESERVED_ATTRIBUTE_NAMES; exports.PolicyViolationError = PolicyViolationError; exports.SYSTEM_ATTRIBUTES = SYSTEM_ATTRIBUTES; exports.getSystemAttributeList = getSystemAttributeList; exports.isSystemAttribute = isSystemAttribute; exports.isSystemAttributeObject = isSystemAttributeObject; exports.asTenantId = asTenantId; exports.asUserId = asUserId; exports.generateId = generateId; exports.generatePrefixedId = generatePrefixedId; exports.EMPTY_VALUE_PLACEHOLDER = EMPTY_VALUE_PLACEHOLDER; exports.formatAttributeValue = formatAttributeValue; exports.SchemaErrorCode = SchemaErrorCode; exports.SchemaError = SchemaError; exports.NotFoundError = NotFoundError; exports.ObjectNotFoundError = ObjectNotFoundError; exports.AttributeNotFoundError = AttributeNotFoundError; exports.RecordNotFoundError = RecordNotFoundError; exports.UserProfileNotFoundError = UserProfileNotFoundError; exports.FileNotFoundError = FileNotFoundError; exports.ValidationError = ValidationError; exports.ProtectedResourceError = ProtectedResourceError; exports.SyncError = SyncError; exports.NotSystemObjectError = NotSystemObjectError; exports.DuplicateError = DuplicateError; exports.isSchemaError = isSchemaError; exports.isNotFoundError = isNotFoundError; exports.isValidationError = isValidationError; exports.isProtectedResourceError = isProtectedResourceError; exports.ForbiddenError = ForbiddenError; exports.ProtectedRoleError = ProtectedRoleError; exports.RoleNotFoundError = RoleNotFoundError; exports.isForbiddenError = isForbiddenError; exports.text = text; exports.textarea = textarea; exports.richtext = richtext; exports.number = number; exports.checkbox = checkbox; exports.date = date; exports.phone = phone; exports.currency = currency; exports.status = status; exports.select = select; exports.multiselect = multiselect; exports.location = location; exports.file = file; exports.user = user; exports.relation = relation; exports.rating = rating; exports.formula = formula; exports.rollup = rollup; exports.FlowRowBuilder = FlowRowBuilder; exports.FlowPageBuilder = FlowPageBuilder; exports.FlowBuilder = FlowBuilder; exports.flow = flow; exports.ObjectBuilder = ObjectBuilder; exports.object = object; exports.GroupBuilder = GroupBuilder; exports.DirectTableTabConfig = DirectTableTabConfig; exports.InverseTableTabConfig = InverseTableTabConfig; exports.CustomTabConfig = CustomTabConfig; exports.NotesTabConfig = NotesTabConfig; exports.ActivityTabConfig = ActivityTabConfig; exports.TabBuilder = TabBuilder; exports.ViewBuilder = ViewBuilder; exports.view = view; exports.group = group; exports.registry = registry; exports.textConfigSchema = textConfigSchema; exports.textareaConfigSchema = textareaConfigSchema; exports.richtextConfigSchema = richtextConfigSchema; exports.numberConfigSchema = numberConfigSchema; exports.checkboxConfigSchema = checkboxConfigSchema; exports.dateConfigSchema = dateConfigSchema; exports.phoneConfigSchema = phoneConfigSchema; exports.currencyConfigSchema = currencyConfigSchema; exports.statusConfigSchema = statusConfigSchema; exports.locationConfigSchema = locationConfigSchema; exports.selectConfigSchema = selectConfigSchema; exports.multiselectConfigSchema = multiselectConfigSchema; exports.fileConfigSchema = fileConfigSchema; exports.userConfigSchema = userConfigSchema; exports.relationConfigSchema = relationConfigSchema; exports.ratingConfigSchema = ratingConfigSchema; exports.formulaConfigSchema = formulaConfigSchema; exports.rollupConfigSchema = rollupConfigSchema; exports.attributeConfigSchemas = attributeConfigSchemas; exports.getAttributeConfigSchema = getAttributeConfigSchema; exports.validateAttributeConfig = validateAttributeConfig; exports.parseAttributeConfig = parseAttributeConfig; exports.safeParseAttributeConfig = safeParseAttributeConfig; exports.createTextValidator = createTextValidator; exports.createNumberValidator = createNumberValidator; exports.createCheckboxValidator = createCheckboxValidator; exports.createDateValidator = createDateValidator; exports.createPhoneValidator = createPhoneValidator; exports.createCurrencyValidator = createCurrencyValidator; exports.createStatusValidator = createStatusValidator; exports.createSelectValidator = createSelectValidator; exports.createMultiselectValidator = createMultiselectValidator; exports.createLocationValidator = createLocationValidator; exports.createFileValidator = createFileValidator; exports.createUserValidator = createUserValidator; exports.createSingleRelationValidator = createSingleRelationValidator; exports.createMultiRelationValidator = createMultiRelationValidator; exports.createRelationValidator = createRelationValidator; exports.createRatingValidator = createRatingValidator; exports.createFormulaValidator = createFormulaValidator; exports.createRollupValidator = createRollupValidator; exports.createTextAreaValidator = createTextAreaValidator; exports.createRichtextValidator = createRichtextValidator; exports.createAttributeValidator = createAttributeValidator; exports.createObjectValidator = createObjectValidator; exports.validateAttribute = validateAttribute; exports.validateObject = validateObject; exports.validateObjectOrThrow = validateObjectOrThrow; exports.createDraftValidator = createDraftValidator; exports.validateDraft = validateDraft; exports.validateDraftOrThrow = validateDraftOrThrow; exports.getMissingRequiredAttributes = getMissingRequiredAttributes; exports.isRecordComplete = isRecordComplete; exports.computeRecordStatus = computeRecordStatus; exports.formatRecord = formatRecord; exports.formatRecords = formatRecords; exports.createDefaultState = createDefaultState; exports.SHORTCUT_TO_FILTER_OPERATOR = SHORTCUT_TO_FILTER_OPERATOR; exports.QueryNoResultError = QueryNoResultError; exports.QueryMultipleResultsError = QueryMultipleResultsError; exports.TenantContextError = TenantContextError; exports.getContext = getContext; exports.getTenantId = getTenantId; exports.getUserId = getUserId; exports.hasContext = hasContext; exports.runWithContext = runWithContext; exports.withTenantContext = withTenantContext; exports.QueryBuilder = QueryBuilder; exports.createQueryBuilder = createQueryBuilder; exports.evaluateFormula = evaluateFormula; exports.evaluateFormulaWithResult = evaluateFormulaWithResult; exports.formatFormulaResult = formatFormulaResult; exports.evaluateFormulaAttribute = evaluateFormulaAttribute; exports.validateFormulaExpression = validateFormulaExpression; exports.extractFormulaVariables = extractFormulaVariables; exports.extractRelationReferences = extractRelationReferences; exports.extractRelationNames = extractRelationNames; exports.hasRelationReferences = hasRelationReferences; exports.flattenRelationsForEval = flattenRelationsForEval; exports.evaluateFormulaWithRelations = evaluateFormulaWithRelations; exports.evaluateFormulaAttributeWithRelations = evaluateFormulaAttributeWithRelations; exports.InvalidPathError = InvalidPathError; exports.MaxDepthExceededError = MaxDepthExceededError; exports.parsePath = parsePath; exports.validatePath = validatePath; exports.pathHasManyCardinality = pathHasManyCardinality; exports.getPathDepth = getPathDepth; exports.getTargetAttributeName = getTargetAttributeName; exports.getRelationPath = getRelationPath; exports.traversePath = traversePath; exports.resolveSingleValue = resolveSingleValue; exports.resolveMultiplePaths = resolveMultiplePaths; exports.NoopHookRegistry = NoopHookRegistry; exports.DEFAULT_LABEL_FALLBACK = DEFAULT_LABEL_FALLBACK; exports.renderLabelExpression = renderLabelExpression; exports.isLabelExpression = isLabelExpression; exports.extractAttributeNames = extractAttributeNames; exports.enrichValuesWithSelectLabels = enrichValuesWithSelectLabels; exports.createMockAdapter = createMockAdapter; exports.PolicyRegistry = PolicyRegistry; exports.defaultPolicyRegistry = defaultPolicyRegistry; exports.notesPolicy = notesPolicy; exports.buildAuditChanges = buildAuditChanges; exports.TenantAwareService = TenantAwareService; exports.TenantAwareRepository = TenantAwareRepository; exports.AuditService = AuditService; exports.FileService = FileService; exports.FlowService = FlowService; exports.GeocodingService = GeocodingService; exports.GlobalSearchService = GlobalSearchService; exports.ObjectSchemaService = ObjectSchemaService; exports.PermissionService = PermissionService; exports.RelationService = RelationService; exports.RollupService = RollupService; exports.UserService = UserService; exports.RecordService = RecordService; exports.RelationResolverService = RelationResolverService; exports.RollupScheduler = RollupScheduler; exports.UserProfileService = UserProfileService; exports.ViewService = ViewService; exports.syncNativeViews = syncNativeViews; exports.verifyNativeViewsSync = verifyNativeViewsSync; exports.getViewSyncPreview = getViewSyncPreview; exports.syncNativeObjects = syncNativeObjects; exports.verifyNativeObjectsSync = verifyNativeObjectsSync; exports.getSyncPreview = getSyncPreview; exports.syncAll = syncAll;
@@ -1512,6 +1512,23 @@ function createMockUserProfilesRepository(stores) {
1512
1512
  stores.userProfiles.set(id, profile);
1513
1513
  }
1514
1514
  return Promise.resolve();
1515
+ },
1516
+ invite(data) {
1517
+ const tenantId = getTenantId();
1518
+ const profile = {
1519
+ id: generateId(),
1520
+ tenantId,
1521
+ authId: `invited-${generateId()}`,
1522
+ email: data.email,
1523
+ firstName: data.firstName,
1524
+ lastName: data.lastName,
1525
+ role: data.role ?? "member",
1526
+ status: "pending",
1527
+ createdAt: /* @__PURE__ */ new Date(),
1528
+ updatedAt: /* @__PURE__ */ new Date()
1529
+ };
1530
+ stores.userProfiles.set(profile.id, profile);
1531
+ return Promise.resolve(profile);
1515
1532
  }
1516
1533
  };
1517
1534
  }
@@ -5151,11 +5168,10 @@ var GroupBuilder = class {
5151
5168
  return this.data;
5152
5169
  }
5153
5170
  };
5154
- var TableTabConfig = class {
5155
- /** @internal */
5156
- constructor(view2, base, relation2) {
5171
+ var BaseTableTabConfig = class {
5172
+ constructor(view2, tabData) {
5157
5173
  this.view = view2;
5158
- this.tabData = { ...base, type: "table", relation: relation2, columns: [] };
5174
+ this.tabData = tabData;
5159
5175
  }
5160
5176
  /**
5161
5177
  * Set columns to display
@@ -5203,11 +5219,22 @@ var TableTabConfig = class {
5203
5219
  return this;
5204
5220
  }
5205
5221
  /**
5206
- * Set default sort
5222
+ * Set default sort rules
5223
+ * @example .sorts([{ attribute: "lastName", direction: "asc" }])
5224
+ */
5225
+ sorts(value) {
5226
+ this.tabData.sorts = value;
5227
+ return this;
5228
+ }
5229
+ /**
5230
+ * Add a single sort rule (convenience method)
5207
5231
  * @example .sort("lastName") or .sort("createdAt", "desc")
5208
5232
  */
5209
- sort(field, order = "asc") {
5210
- this.tabData.sort = { field, order };
5233
+ sort(attribute, direction = "asc") {
5234
+ if (!this.tabData.sorts) {
5235
+ this.tabData.sorts = [];
5236
+ }
5237
+ this.tabData.sorts.push({ attribute, direction });
5211
5238
  return this;
5212
5239
  }
5213
5240
  /**
@@ -5238,6 +5265,31 @@ var TableTabConfig = class {
5238
5265
  this.view._addTab(this.tabData);
5239
5266
  }
5240
5267
  };
5268
+ var DirectTableTabConfig = class extends BaseTableTabConfig {
5269
+ /** @internal */
5270
+ constructor(view2, base, relationAttribute) {
5271
+ super(view2, {
5272
+ ...base,
5273
+ type: "table",
5274
+ relationMode: "direct",
5275
+ relationAttribute,
5276
+ columns: []
5277
+ });
5278
+ }
5279
+ };
5280
+ var InverseTableTabConfig = class extends BaseTableTabConfig {
5281
+ /** @internal */
5282
+ constructor(view2, base, sourceObject, relationAttribute) {
5283
+ super(view2, {
5284
+ ...base,
5285
+ type: "table",
5286
+ relationMode: "inverse",
5287
+ sourceObject,
5288
+ relationAttribute,
5289
+ columns: []
5290
+ });
5291
+ }
5292
+ };
5241
5293
  var CustomTabConfig = class {
5242
5294
  /** @internal */
5243
5295
  constructor(view2, base, component) {
@@ -5402,11 +5454,27 @@ var TabBuilder = class {
5402
5454
  return this.view._addTab(tab);
5403
5455
  }
5404
5456
  /**
5405
- * Create a table tab for a relation
5406
- * @example .table("contacts").columns("name", "email").crud()
5457
+ * Create a direct table tab for a relation attribute on the current object
5458
+ *
5459
+ * Use this when the current object has a relation attribute pointing to another object.
5460
+ *
5461
+ * @param relationAttribute - Name of the relation attribute on the current object
5462
+ * @example .table("members").columns("name", "email").crud() // Show users from Project.members
5407
5463
  */
5408
- table(relation2) {
5409
- return new TableTabConfig(this.view, this.base, relation2);
5464
+ table(relationAttribute) {
5465
+ return new DirectTableTabConfig(this.view, this.base, relationAttribute);
5466
+ }
5467
+ /**
5468
+ * Create an inverse table tab showing records from another object that have a relation to us
5469
+ *
5470
+ * Use this when another object has a relation attribute pointing to the current object.
5471
+ *
5472
+ * @param sourceObject - Name of the object that has the relation to us
5473
+ * @param relationAttribute - Name of the relation attribute on the source object
5474
+ * @example .tableFrom("contacts", "company").columns("firstName", "lastName") // Show contacts where Contact.company = this
5475
+ */
5476
+ tableFrom(sourceObject, relationAttribute) {
5477
+ return new InverseTableTabConfig(this.view, this.base, sourceObject, relationAttribute);
5410
5478
  }
5411
5479
  /**
5412
5480
  * Create a custom tab with a component
@@ -8444,6 +8512,12 @@ var RecordService = class extends TenantAwareService {
8444
8512
  if (!options?.skipHooks) {
8445
8513
  await this.hookRegistry.execute("beforeUpdate", schema.name, hookCtx);
8446
8514
  }
8515
+ const hookModifiedValues = {};
8516
+ for (const key of Object.keys(hookCtx.newValues)) {
8517
+ if (!(key in data) && hookCtx.newValues[key] !== existing.values[key]) {
8518
+ hookModifiedValues[key] = hookCtx.newValues[key];
8519
+ }
8520
+ }
8447
8521
  if (options?.validate !== false) {
8448
8522
  if (options?.partial) {
8449
8523
  validateDraftOrThrow(schema, mergedData);
@@ -8451,16 +8525,23 @@ var RecordService = class extends TenantAwareService {
8451
8525
  validateObjectOrThrow(schema, mergedData);
8452
8526
  }
8453
8527
  if (!options?.skipRelationValidation) {
8454
- await this.relationService.validateRelationsOrThrow(schema, data);
8528
+ await this.relationService.validateRelationsOrThrow(schema, {
8529
+ ...data,
8530
+ ...hookModifiedValues
8531
+ });
8455
8532
  }
8456
8533
  if (!options?.skipUserValidation) {
8457
- await this.userService.validateUsersOrThrow(schema, data);
8534
+ await this.userService.validateUsersOrThrow(schema, {
8535
+ ...data,
8536
+ ...hookModifiedValues
8537
+ });
8458
8538
  }
8459
8539
  }
8460
8540
  const completionStatus = computeRecordStatus(schema, mergedData);
8461
8541
  const label = await this.computeLabel(schema, mergedData);
8462
8542
  const updatePayload = {
8463
8543
  ...data,
8544
+ ...hookModifiedValues,
8464
8545
  __completionStatus: completionStatus,
8465
8546
  __label: label,
8466
8547
  __lastUpdatedBy: this.userId
@@ -8482,11 +8563,15 @@ var RecordService = class extends TenantAwareService {
8482
8563
  await this.hookRegistry.execute("afterUpdate", schema.name, afterCtx);
8483
8564
  }
8484
8565
  await this.recalculateParentRollups(updated, schema);
8485
- if (this.auditService && this.userId && changedAttributes.length > 0) {
8486
- const changes = changedAttributes.map((attr) => ({
8566
+ const allChangedAttributes = [
8567
+ ...changedAttributes,
8568
+ ...Object.keys(hookModifiedValues).filter((k) => !changedAttributes.includes(k))
8569
+ ];
8570
+ if (this.auditService && this.userId && allChangedAttributes.length > 0) {
8571
+ const changes = allChangedAttributes.map((attr) => ({
8487
8572
  field: attr,
8488
8573
  oldValue: hookCtx.oldValues?.[attr],
8489
- newValue: data[attr]
8574
+ newValue: hookCtx.newValues[attr]
8490
8575
  }));
8491
8576
  await this.auditService.logRecordAction({
8492
8577
  action: "record.updated",
@@ -9339,6 +9424,48 @@ var UserProfileService = class extends TenantAwareService {
9339
9424
  async isAdmin(profileId) {
9340
9425
  return await this.hasRole(profileId, "admin");
9341
9426
  }
9427
+ /**
9428
+ * Invite a new user by email.
9429
+ *
9430
+ * This method:
9431
+ * 1. Sends an invitation email via the auth provider (e.g., Supabase Auth)
9432
+ * 2. Creates a user profile with status "pending"
9433
+ * 3. Returns the created profile
9434
+ *
9435
+ * The user will receive an email with a link to accept the invitation.
9436
+ * When they click the link, their auth account is activated.
9437
+ *
9438
+ * @param data - Invitation data
9439
+ * @returns Created pending user profile
9440
+ *
9441
+ * @example
9442
+ * ```typescript
9443
+ * const profile = await service.inviteUser({
9444
+ * email: "john@example.com",
9445
+ * firstName: "John",
9446
+ * lastName: "Doe",
9447
+ * role: "member",
9448
+ * redirectTo: "https://app.example.com/welcome",
9449
+ * });
9450
+ * // Email sent automatically, profile.status === "pending"
9451
+ * ```
9452
+ */
9453
+ async inviteUser(data) {
9454
+ const existingEmail = await this.adapter.userProfiles.findByEmail(data.email);
9455
+ if (existingEmail) {
9456
+ throw new Error(`User with email "${data.email}" already exists in this tenant`);
9457
+ }
9458
+ const profile = await this.adapter.userProfiles.invite(data);
9459
+ if (this.auditService && this.userId) {
9460
+ await this.auditService.logUserAction({
9461
+ action: "user.invited",
9462
+ actorId: this.userId,
9463
+ targetUserId: profile.id,
9464
+ targetUserEmail: profile.email
9465
+ });
9466
+ }
9467
+ return profile;
9468
+ }
9342
9469
  };
9343
9470
 
9344
9471
  // src/runtime/services/view.service.ts
@@ -9950,7 +10077,8 @@ export {
9950
10077
  ObjectBuilder,
9951
10078
  object,
9952
10079
  GroupBuilder,
9953
- TableTabConfig,
10080
+ DirectTableTabConfig,
10081
+ InverseTableTabConfig,
9954
10082
  CustomTabConfig,
9955
10083
  NotesTabConfig,
9956
10084
  ActivityTabConfig,