@objectstack/plugin-sharing 6.8.1 → 6.9.0

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/index.js CHANGED
@@ -21,18 +21,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DepartmentGraphService: () => DepartmentGraphService,
24
+ SHARE_LINK_SERVICE: () => import_contracts.SHARE_LINK_SERVICE,
24
25
  SHARING_RULE_HOOK_PACKAGE: () => SHARING_RULE_HOOK_PACKAGE,
26
+ ShareLinkService: () => ShareLinkService,
25
27
  SharingRuleService: () => SharingRuleService,
26
28
  SharingService: () => SharingService,
27
29
  SharingServicePlugin: () => SharingServicePlugin,
28
30
  SysDepartment: () => import_identity2.SysDepartment,
29
31
  SysDepartmentMember: () => import_identity2.SysDepartmentMember,
30
32
  SysRecordShare: () => import_security2.SysRecordShare,
33
+ SysShareLink: () => import_security2.SysShareLink,
31
34
  SysSharingRule: () => import_security2.SysSharingRule,
32
35
  TeamGraphService: () => TeamGraphService,
33
36
  bindRuleHooks: () => bindRuleHooks,
34
37
  buildSharingMiddleware: () => buildSharingMiddleware,
35
38
  expandPrincipal: () => expandPrincipal,
39
+ registerShareLinkRoutes: () => registerShareLinkRoutes,
36
40
  unbindAllRuleHooks: () => unbindAllRuleHooks
37
41
  });
38
42
  module.exports = __toCommonJS(index_exports);
@@ -796,8 +800,435 @@ var SharingRuleService = class {
796
800
  }
797
801
  };
798
802
 
799
- // src/rule-hooks.ts
803
+ // src/share-link-service.ts
800
804
  var SYSTEM_CTX5 = { isSystem: true, roles: [], permissions: [] };
805
+ var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
806
+ var TOKEN_LENGTH = 24;
807
+ var DEFAULT_MAX_EXPIRY_DAYS = 365;
808
+ function generateToken(length = TOKEN_LENGTH) {
809
+ const g = globalThis;
810
+ const bytes = new Uint8Array(length);
811
+ if (g.crypto?.getRandomValues) {
812
+ g.crypto.getRandomValues(bytes);
813
+ } else {
814
+ for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
815
+ }
816
+ let out = "";
817
+ for (let i = 0; i < length; i++) {
818
+ out += TOKEN_ALPHABET[bytes[i] % TOKEN_ALPHABET.length];
819
+ }
820
+ return out;
821
+ }
822
+ function getPolicy(schema) {
823
+ const raw = schema?.publicSharing;
824
+ if (!raw || raw.enabled !== true) {
825
+ return {
826
+ enabled: false,
827
+ allowedAudiences: [],
828
+ allowedPermissions: [],
829
+ redactFields: []
830
+ };
831
+ }
832
+ return {
833
+ enabled: true,
834
+ allowedAudiences: raw.allowedAudiences ?? ["link_only"],
835
+ allowedPermissions: raw.allowedPermissions ?? ["view"],
836
+ maxExpiryDays: typeof raw.maxExpiryDays === "number" ? raw.maxExpiryDays : void 0,
837
+ redactFields: Array.isArray(raw.redactFields) ? raw.redactFields : []
838
+ };
839
+ }
840
+ function normaliseExpiresAt(input, maxDays) {
841
+ if (!input) return null;
842
+ const now = Date.now();
843
+ const cap = now + maxDays * 864e5;
844
+ const m = /^([0-9]+)(s|m|h|d)$/i.exec(input);
845
+ if (m) {
846
+ const n = Number(m[1]);
847
+ const unit = m[2].toLowerCase();
848
+ const ms = unit === "s" ? n * 1e3 : unit === "m" ? n * 6e4 : unit === "h" ? n * 36e5 : n * 864e5;
849
+ const at = now + ms;
850
+ if (at > cap) {
851
+ throw makeError(422, "EXPIRY_TOO_LONG", `expiresAt exceeds the object's max of ${maxDays} days`);
852
+ }
853
+ return new Date(at).toISOString();
854
+ }
855
+ const t = Date.parse(input);
856
+ if (Number.isNaN(t)) {
857
+ throw makeError(422, "INVALID_EXPIRY", `expiresAt is not a valid ISO timestamp or duration: ${input}`);
858
+ }
859
+ if (t > cap) {
860
+ throw makeError(422, "EXPIRY_TOO_LONG", `expiresAt exceeds the object's max of ${maxDays} days`);
861
+ }
862
+ if (t <= now) {
863
+ throw makeError(422, "EXPIRY_IN_PAST", "expiresAt must be in the future");
864
+ }
865
+ return new Date(t).toISOString();
866
+ }
867
+ async function defaultHashPassword(password) {
868
+ const g = globalThis;
869
+ const subtle = g.crypto?.subtle;
870
+ const salt = generateToken(16);
871
+ if (!subtle) {
872
+ return `weak$${salt}$${password}`;
873
+ }
874
+ const enc = new TextEncoder();
875
+ const buf = await subtle.digest("SHA-256", enc.encode(salt + ":" + password));
876
+ const hex = Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
877
+ return `sha256$${salt}$${hex}`;
878
+ }
879
+ async function defaultVerifyPassword(password, hash) {
880
+ if (hash.startsWith("weak$")) {
881
+ const [, , stored] = hash.split("$");
882
+ return stored === password;
883
+ }
884
+ if (hash.startsWith("sha256$")) {
885
+ const [, salt, expected] = hash.split("$");
886
+ const g = globalThis;
887
+ const subtle = g.crypto?.subtle;
888
+ if (!subtle) return false;
889
+ const enc = new TextEncoder();
890
+ const buf = await subtle.digest("SHA-256", enc.encode(salt + ":" + password));
891
+ const hex = Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
892
+ return hex === expected;
893
+ }
894
+ return false;
895
+ }
896
+ function makeError(status, code, message) {
897
+ const err = new Error(message);
898
+ err.status = status;
899
+ err.code = code;
900
+ return err;
901
+ }
902
+ var ShareLinkService = class {
903
+ constructor(opts) {
904
+ this.engine = opts.engine;
905
+ this.permissive = opts.permissive ?? false;
906
+ this.hashPassword = opts.hashPassword ?? defaultHashPassword;
907
+ this.verifyPassword = opts.verifyPassword ?? defaultVerifyPassword;
908
+ }
909
+ async createLink(input, context) {
910
+ if (!input.object) throw makeError(400, "VALIDATION_FAILED", "object is required");
911
+ if (!input.recordId) throw makeError(400, "VALIDATION_FAILED", "recordId is required");
912
+ const schema = this.engine.getSchema?.(input.object);
913
+ const policy = getPolicy(schema);
914
+ if (!policy.enabled && !this.permissive && !context.isSystem) {
915
+ throw makeError(
916
+ 422,
917
+ "SHARING_NOT_ENABLED",
918
+ `Object '${input.object}' has not enabled publicSharing in its schema`
919
+ );
920
+ }
921
+ const permission = input.permission ?? "view";
922
+ if (policy.enabled && policy.allowedPermissions.length > 0 && !policy.allowedPermissions.includes(permission)) {
923
+ throw makeError(
924
+ 422,
925
+ "PERMISSION_NOT_ALLOWED",
926
+ `Object '${input.object}' does not allow share permission '${permission}'. Allowed: ${policy.allowedPermissions.join(", ")}`
927
+ );
928
+ }
929
+ const audience = input.audience ?? "link_only";
930
+ if (policy.enabled && policy.allowedAudiences.length > 0 && !policy.allowedAudiences.includes(audience)) {
931
+ throw makeError(
932
+ 422,
933
+ "AUDIENCE_NOT_ALLOWED",
934
+ `Object '${input.object}' does not allow audience '${audience}'. Allowed: ${policy.allowedAudiences.join(", ")}`
935
+ );
936
+ }
937
+ if (audience === "email" && (!input.emailAllowlist || input.emailAllowlist.length === 0)) {
938
+ throw makeError(400, "VALIDATION_FAILED", "emailAllowlist is required when audience=email");
939
+ }
940
+ const exists = await this.engine.find(input.object, {
941
+ where: { id: input.recordId },
942
+ fields: ["id"],
943
+ limit: 1,
944
+ context: SYSTEM_CTX5
945
+ });
946
+ if (!Array.isArray(exists) || exists.length === 0) {
947
+ throw makeError(404, "RECORD_NOT_FOUND", `${input.object}/${input.recordId} does not exist`);
948
+ }
949
+ const maxDays = policy.maxExpiryDays ?? DEFAULT_MAX_EXPIRY_DAYS;
950
+ const expires_at = normaliseExpiresAt(input.expiresAt, maxDays);
951
+ const passwordHash = input.password ? await this.hashPassword(input.password) : null;
952
+ const row = {
953
+ id: `shl_${generateToken(16)}`,
954
+ token: generateToken(TOKEN_LENGTH),
955
+ object_name: input.object,
956
+ record_id: input.recordId,
957
+ permission,
958
+ audience,
959
+ expires_at,
960
+ email_allowlist: input.emailAllowlist && input.emailAllowlist.length > 0 ? input.emailAllowlist.map((e) => e.trim().toLowerCase()).filter(Boolean) : null,
961
+ password_hash: passwordHash,
962
+ redact_fields: input.redactFields && input.redactFields.length > 0 ? input.redactFields : null,
963
+ label: input.label ?? null,
964
+ revoked_at: null,
965
+ created_by: context.userId ?? null,
966
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
967
+ last_used_at: null,
968
+ use_count: 0
969
+ };
970
+ await this.engine.insert("sys_share_link", row, { context: SYSTEM_CTX5 });
971
+ return row;
972
+ }
973
+ async revokeLink(idOrToken, _context) {
974
+ if (!idOrToken) throw makeError(400, "VALIDATION_FAILED", "id or token is required");
975
+ const filter = idOrToken.startsWith("shl_") ? { id: idOrToken } : { token: idOrToken };
976
+ const rows = await this.engine.find("sys_share_link", {
977
+ where: filter,
978
+ fields: ["id", "revoked_at"],
979
+ limit: 1,
980
+ context: SYSTEM_CTX5
981
+ });
982
+ const row = Array.isArray(rows) ? rows[0] : void 0;
983
+ if (!row) return;
984
+ if (row.revoked_at) return;
985
+ await this.engine.update(
986
+ "sys_share_link",
987
+ { id: row.id, revoked_at: (/* @__PURE__ */ new Date()).toISOString() },
988
+ { context: SYSTEM_CTX5 }
989
+ );
990
+ }
991
+ async listLinks(filter, context) {
992
+ const where = {};
993
+ if (filter.object) where.object_name = filter.object;
994
+ if (filter.recordId) where.record_id = filter.recordId;
995
+ if (filter.createdBy) where.created_by = filter.createdBy;
996
+ if (!filter.includeRevoked) where.revoked_at = null;
997
+ const rows = await this.engine.find("sys_share_link", {
998
+ where,
999
+ limit: 200,
1000
+ sort: [{ field: "created_at", order: "desc" }],
1001
+ context: context.isSystem ? SYSTEM_CTX5 : context
1002
+ });
1003
+ return Array.isArray(rows) ? rows : [];
1004
+ }
1005
+ async resolveToken(token, probe = {}) {
1006
+ if (!token || typeof token !== "string" || token.length < 8) return null;
1007
+ const rows = await this.engine.find("sys_share_link", {
1008
+ where: { token },
1009
+ limit: 1,
1010
+ context: SYSTEM_CTX5
1011
+ });
1012
+ const row = Array.isArray(rows) ? rows[0] : void 0;
1013
+ if (!row) return null;
1014
+ if (row.revoked_at) return null;
1015
+ if (row.expires_at && Date.parse(row.expires_at) <= Date.now()) return null;
1016
+ if (row.audience === "signed_in" && !probe.signedInUserId) return null;
1017
+ if (row.audience === "email") {
1018
+ const allow = row.email_allowlist ?? [];
1019
+ const supplied = (probe.recipientEmail ?? "").trim().toLowerCase();
1020
+ if (!supplied || !allow.includes(supplied)) return null;
1021
+ }
1022
+ if (row.password_hash) {
1023
+ if (!probe.providedPassword) return null;
1024
+ const ok = await this.verifyPassword(probe.providedPassword, row.password_hash);
1025
+ if (!ok) return null;
1026
+ }
1027
+ const schema = this.engine.getSchema?.(row.object_name);
1028
+ const policy = getPolicy(schema);
1029
+ const redactFields = Array.from(
1030
+ /* @__PURE__ */ new Set([...policy.redactFields ?? [], ...row.redact_fields ?? []])
1031
+ );
1032
+ try {
1033
+ await this.engine.update(
1034
+ "sys_share_link",
1035
+ {
1036
+ id: row.id,
1037
+ last_used_at: (/* @__PURE__ */ new Date()).toISOString(),
1038
+ use_count: (row.use_count ?? 0) + 1
1039
+ },
1040
+ { context: SYSTEM_CTX5 }
1041
+ );
1042
+ } catch {
1043
+ }
1044
+ return { link: row, redactFields };
1045
+ }
1046
+ };
1047
+
1048
+ // src/share-link-routes.ts
1049
+ var SYSTEM_CTX6 = { isSystem: true, roles: [], permissions: [] };
1050
+ var defaultContext = (req) => {
1051
+ const header = (name) => {
1052
+ const v = req.headers?.[name];
1053
+ return Array.isArray(v) ? v[0] : v;
1054
+ };
1055
+ return {
1056
+ userId: header("x-user-id"),
1057
+ tenantId: header("x-tenant-id")
1058
+ };
1059
+ };
1060
+ function sendError(res, status, code, message) {
1061
+ res.status(status).json({ error: { code, message } });
1062
+ }
1063
+ function applyRedaction(record, redactFields) {
1064
+ if (!record || typeof record !== "object" || redactFields.length === 0) return record;
1065
+ if (Array.isArray(record)) return record.map((r) => applyRedaction(r, redactFields));
1066
+ const out = {};
1067
+ for (const [k, v] of Object.entries(record)) {
1068
+ if (redactFields.includes(k)) continue;
1069
+ out[k] = v;
1070
+ }
1071
+ return out;
1072
+ }
1073
+ function registerShareLinkRoutes(http, service, engine, opts = {}) {
1074
+ const base = opts.basePath ?? "/api/v1/share-links";
1075
+ const ctxOf = opts.contextFromRequest ?? defaultContext;
1076
+ http.post(base, (async (req, res) => {
1077
+ try {
1078
+ const ctx = ctxOf(req);
1079
+ const body = req.body ?? {};
1080
+ if (!body.object || !body.recordId) {
1081
+ return sendError(res, 400, "VALIDATION_FAILED", "object and recordId are required");
1082
+ }
1083
+ const link = await service.createLink(
1084
+ {
1085
+ object: body.object,
1086
+ recordId: body.recordId,
1087
+ permission: body.permission,
1088
+ audience: body.audience,
1089
+ expiresAt: body.expiresAt ?? null,
1090
+ emailAllowlist: body.emailAllowlist,
1091
+ password: body.password,
1092
+ redactFields: body.redactFields,
1093
+ label: body.label
1094
+ },
1095
+ ctx
1096
+ );
1097
+ await res.status(201).json({ link });
1098
+ } catch (err) {
1099
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to create link");
1100
+ }
1101
+ }));
1102
+ http.get(base, (async (req, res) => {
1103
+ try {
1104
+ const ctx = ctxOf(req);
1105
+ const q = req.query ?? {};
1106
+ const link = await service.listLinks(
1107
+ {
1108
+ object: typeof q.object === "string" ? q.object : void 0,
1109
+ recordId: typeof q.recordId === "string" ? q.recordId : void 0,
1110
+ createdBy: typeof q.createdBy === "string" ? q.createdBy : void 0,
1111
+ includeRevoked: q.includeRevoked === "true" || q.includeRevoked === "1"
1112
+ },
1113
+ ctx
1114
+ );
1115
+ await res.json({ links: link });
1116
+ } catch (err) {
1117
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to list links");
1118
+ }
1119
+ }));
1120
+ http.delete(`${base}/:idOrToken`, (async (req, res) => {
1121
+ try {
1122
+ const ctx = ctxOf(req);
1123
+ await service.revokeLink(req.params.idOrToken, ctx);
1124
+ await res.status(200).json({ ok: true });
1125
+ } catch (err) {
1126
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to revoke link");
1127
+ }
1128
+ }));
1129
+ http.get(`${base}/:token/resolve`, (async (req, res) => {
1130
+ try {
1131
+ const q = req.query ?? {};
1132
+ const signedInUserId = (() => {
1133
+ const v = req.headers?.["x-user-id"];
1134
+ return Array.isArray(v) ? v[0] : v;
1135
+ })();
1136
+ const recipientEmail = typeof q.email === "string" ? q.email : void 0;
1137
+ const providedPassword = typeof q.password === "string" ? q.password : (() => {
1138
+ const v = req.headers?.["x-share-password"];
1139
+ return Array.isArray(v) ? v[0] : v;
1140
+ })();
1141
+ const resolved = await service.resolveToken(req.params.token, {
1142
+ signedInUserId,
1143
+ recipientEmail,
1144
+ providedPassword
1145
+ });
1146
+ if (!resolved) {
1147
+ const probe = await engine.find("sys_share_link", {
1148
+ where: { token: req.params.token },
1149
+ limit: 1,
1150
+ context: SYSTEM_CTX6
1151
+ });
1152
+ const row = Array.isArray(probe) && probe[0] ? probe[0] : null;
1153
+ if (row && !row.revoked_at && (!row.expires_at || Date.parse(row.expires_at) > Date.now())) {
1154
+ if (row.password_hash) {
1155
+ return sendError(
1156
+ res,
1157
+ 401,
1158
+ providedPassword ? "WRONG_PASSWORD" : "NEEDS_PASSWORD",
1159
+ providedPassword ? "Incorrect password" : "This link requires a password"
1160
+ );
1161
+ }
1162
+ if (row.audience === "signed_in" && !signedInUserId) {
1163
+ return sendError(res, 401, "SIGN_IN_REQUIRED", "Please sign in to view this link");
1164
+ }
1165
+ }
1166
+ if (row && (row.revoked_at || row.expires_at && Date.parse(row.expires_at) <= Date.now())) {
1167
+ return sendError(res, 410, "EXPIRED_OR_REVOKED", "Share link has expired or been revoked");
1168
+ }
1169
+ return sendError(res, 404, "INVALID_OR_EXPIRED", "Share link is invalid, expired, or revoked");
1170
+ }
1171
+ const rows = await engine.find(resolved.link.object_name, {
1172
+ where: { id: resolved.link.record_id },
1173
+ limit: 1,
1174
+ context: SYSTEM_CTX6
1175
+ });
1176
+ const record = Array.isArray(rows) && rows[0] ? rows[0] : null;
1177
+ if (!record) {
1178
+ return sendError(res, 410, "RECORD_GONE", "The shared record no longer exists");
1179
+ }
1180
+ await res.json({
1181
+ record: applyRedaction(record, resolved.redactFields),
1182
+ link: {
1183
+ id: resolved.link.id,
1184
+ token: resolved.link.token,
1185
+ object_name: resolved.link.object_name,
1186
+ record_id: resolved.link.record_id,
1187
+ permission: resolved.link.permission,
1188
+ audience: resolved.link.audience,
1189
+ expires_at: resolved.link.expires_at,
1190
+ label: resolved.link.label,
1191
+ created_at: resolved.link.created_at
1192
+ },
1193
+ redactFields: resolved.redactFields
1194
+ });
1195
+ } catch (err) {
1196
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to resolve link");
1197
+ }
1198
+ }));
1199
+ http.get(`${base}/:token/messages`, (async (req, res) => {
1200
+ try {
1201
+ const password = typeof req.query?.password === "string" ? req.query.password : void 0;
1202
+ const resolved = await service.resolveToken(req.params.token, { providedPassword: password });
1203
+ if (!resolved) {
1204
+ sendError(res, 404, "NOT_FOUND", "Share link not found");
1205
+ return;
1206
+ }
1207
+ if (resolved.link.object_name !== "ai_conversations") {
1208
+ sendError(res, 400, "UNSUPPORTED", "This share link does not expose messages");
1209
+ return;
1210
+ }
1211
+ const SYSTEM_CTX8 = { isSystem: true, roles: [], permissions: [] };
1212
+ const rows = await engine.find("ai_messages", {
1213
+ where: { conversation_id: resolved.link.record_id },
1214
+ sort: [{ field: "created_at", direction: "asc" }],
1215
+ limit: 500,
1216
+ context: SYSTEM_CTX8
1217
+ });
1218
+ res.status(200).json({ data: rows ?? [] });
1219
+ } catch (err) {
1220
+ sendError(
1221
+ res,
1222
+ err?.status ?? 500,
1223
+ err?.code ?? "INTERNAL",
1224
+ err?.message ?? "Failed to load messages"
1225
+ );
1226
+ }
1227
+ }));
1228
+ }
1229
+
1230
+ // src/rule-hooks.ts
1231
+ var SYSTEM_CTX7 = { isSystem: true, roles: [], permissions: [] };
801
1232
  var SHARING_RULE_HOOK_PACKAGE = "plugin-sharing:rules";
802
1233
  function bindRuleHooks(engine, service, rules, logger) {
803
1234
  const objects = /* @__PURE__ */ new Set();
@@ -812,7 +1243,7 @@ function bindRuleHooks(engine, service, rules, logger) {
812
1243
  const data = ctx?.result ?? ctx?.input?.data ?? {};
813
1244
  const id = String(data?.id ?? ctx?.input?.id ?? "");
814
1245
  if (!id) return;
815
- await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX5);
1246
+ await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX7);
816
1247
  } catch (err) {
817
1248
  logger?.warn?.("[sharing-rule] hook evaluation failed", { object: objectName, error: err?.message });
818
1249
  }
@@ -846,7 +1277,7 @@ var SharingServicePlugin = class {
846
1277
  scope: "system",
847
1278
  defaultDatasource: "cloud",
848
1279
  namespace: "sys",
849
- objects: [import_security.SysRecordShare, import_security.SysSharingRule, import_identity.SysDepartment, import_identity.SysDepartmentMember]
1280
+ objects: [import_security.SysRecordShare, import_security.SysSharingRule, import_identity.SysDepartment, import_identity.SysDepartmentMember, import_security.SysShareLink]
850
1281
  });
851
1282
  ctx.logger.info("SharingServicePlugin: schema registered");
852
1283
  }
@@ -898,6 +1329,31 @@ var SharingServicePlugin = class {
898
1329
  } catch (err) {
899
1330
  ctx.logger.warn("SharingServicePlugin: sharing-rule subsystem not started", { error: err?.message });
900
1331
  }
1332
+ try {
1333
+ this.linkService = new ShareLinkService({ engine });
1334
+ ctx.registerService("shareLinks", this.linkService);
1335
+ if (this.options.registerShareLinkRoutes !== false) {
1336
+ let http = null;
1337
+ try {
1338
+ http = ctx.getService("http-server");
1339
+ } catch {
1340
+ }
1341
+ if (http) {
1342
+ registerShareLinkRoutes(http, this.linkService, engine, {
1343
+ basePath: this.options.shareLinkBasePath
1344
+ });
1345
+ ctx.logger.info(
1346
+ "SharingServicePlugin: share-link routes mounted at " + (this.options.shareLinkBasePath ?? "/api/v1/share-links")
1347
+ );
1348
+ } else {
1349
+ ctx.logger.warn(
1350
+ 'SharingServicePlugin: no HTTP server \u2014 share-link REST routes not registered. ShareLinkService is still reachable via kernel.getService("shareLinks").'
1351
+ );
1352
+ }
1353
+ }
1354
+ } catch (err) {
1355
+ ctx.logger.warn("SharingServicePlugin: share-link subsystem not started", { error: err?.message });
1356
+ }
901
1357
  });
902
1358
  }
903
1359
  };
@@ -960,21 +1416,28 @@ function inferTargetId(data, options) {
960
1416
  }
961
1417
  return void 0;
962
1418
  }
1419
+
1420
+ // src/index.ts
1421
+ var import_contracts = require("@objectstack/spec/contracts");
963
1422
  // Annotate the CommonJS export names for ESM import in node:
964
1423
  0 && (module.exports = {
965
1424
  DepartmentGraphService,
1425
+ SHARE_LINK_SERVICE,
966
1426
  SHARING_RULE_HOOK_PACKAGE,
1427
+ ShareLinkService,
967
1428
  SharingRuleService,
968
1429
  SharingService,
969
1430
  SharingServicePlugin,
970
1431
  SysDepartment,
971
1432
  SysDepartmentMember,
972
1433
  SysRecordShare,
1434
+ SysShareLink,
973
1435
  SysSharingRule,
974
1436
  TeamGraphService,
975
1437
  bindRuleHooks,
976
1438
  buildSharingMiddleware,
977
1439
  expandPrincipal,
1440
+ registerShareLinkRoutes,
978
1441
  unbindAllRuleHooks
979
1442
  });
980
1443
  //# sourceMappingURL=index.js.map