@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.
- package/dist/{chunk-CDWZ6X5U.js → chunk-5WYGAQYH.js} +145 -17
- package/dist/{chunk-C4QEZGBF.mjs → chunk-E2CTZTQX.mjs} +145 -17
- package/dist/index.d.mts +71 -17
- package/dist/index.d.ts +71 -17
- package/dist/index.js +16 -6
- package/dist/index.mjs +13 -3
- package/dist/{runtime-hbTw33Gx.d.mts → runtime-DyTAIaum.d.mts} +114 -15
- package/dist/{runtime-hbTw33Gx.d.ts → runtime-DyTAIaum.d.ts} +114 -15
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -2
- package/dist/runtime.mjs +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
5155
|
-
|
|
5156
|
-
constructor(view2, base, relation2) {
|
|
5171
|
+
var BaseTableTabConfig = class {
|
|
5172
|
+
constructor(view2, tabData) {
|
|
5157
5173
|
this.view = view2;
|
|
5158
|
-
this.tabData =
|
|
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(
|
|
5210
|
-
this.tabData.
|
|
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
|
-
*
|
|
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
|
-
|
|
5409
|
-
return new
|
|
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,
|
|
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,
|
|
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
|
-
|
|
8486
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
5155
|
-
|
|
5156
|
-
constructor(view2, base, relation2) {
|
|
5171
|
+
var BaseTableTabConfig = class {
|
|
5172
|
+
constructor(view2, tabData) {
|
|
5157
5173
|
this.view = view2;
|
|
5158
|
-
this.tabData =
|
|
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(
|
|
5210
|
-
this.tabData.
|
|
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
|
-
*
|
|
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(
|
|
5409
|
-
return new
|
|
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,
|
|
8528
|
+
await this.relationService.validateRelationsOrThrow(schema, {
|
|
8529
|
+
...data,
|
|
8530
|
+
...hookModifiedValues
|
|
8531
|
+
});
|
|
8455
8532
|
}
|
|
8456
8533
|
if (!options?.skipUserValidation) {
|
|
8457
|
-
await this.userService.validateUsersOrThrow(schema,
|
|
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
|
-
|
|
8486
|
-
|
|
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:
|
|
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
|
-
|
|
10080
|
+
DirectTableTabConfig,
|
|
10081
|
+
InverseTableTabConfig,
|
|
9954
10082
|
CustomTabConfig,
|
|
9955
10083
|
NotesTabConfig,
|
|
9956
10084
|
ActivityTabConfig,
|