@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { SysRecordShare as SysRecordShare2, SysSharingRule as SysSharingRule2 } from "@objectstack/platform-objects/security";
2
+ import { SysRecordShare as SysRecordShare2, SysSharingRule as SysSharingRule2, SysShareLink as SysShareLink2 } from "@objectstack/platform-objects/security";
3
3
  import { SysDepartment as SysDepartment2, SysDepartmentMember as SysDepartmentMember2 } from "@objectstack/platform-objects/identity";
4
4
 
5
5
  // src/sharing-service.ts
@@ -759,8 +759,435 @@ var SharingRuleService = class {
759
759
  }
760
760
  };
761
761
 
762
- // src/rule-hooks.ts
762
+ // src/share-link-service.ts
763
763
  var SYSTEM_CTX5 = { isSystem: true, roles: [], permissions: [] };
764
+ var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
765
+ var TOKEN_LENGTH = 24;
766
+ var DEFAULT_MAX_EXPIRY_DAYS = 365;
767
+ function generateToken(length = TOKEN_LENGTH) {
768
+ const g = globalThis;
769
+ const bytes = new Uint8Array(length);
770
+ if (g.crypto?.getRandomValues) {
771
+ g.crypto.getRandomValues(bytes);
772
+ } else {
773
+ for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
774
+ }
775
+ let out = "";
776
+ for (let i = 0; i < length; i++) {
777
+ out += TOKEN_ALPHABET[bytes[i] % TOKEN_ALPHABET.length];
778
+ }
779
+ return out;
780
+ }
781
+ function getPolicy(schema) {
782
+ const raw = schema?.publicSharing;
783
+ if (!raw || raw.enabled !== true) {
784
+ return {
785
+ enabled: false,
786
+ allowedAudiences: [],
787
+ allowedPermissions: [],
788
+ redactFields: []
789
+ };
790
+ }
791
+ return {
792
+ enabled: true,
793
+ allowedAudiences: raw.allowedAudiences ?? ["link_only"],
794
+ allowedPermissions: raw.allowedPermissions ?? ["view"],
795
+ maxExpiryDays: typeof raw.maxExpiryDays === "number" ? raw.maxExpiryDays : void 0,
796
+ redactFields: Array.isArray(raw.redactFields) ? raw.redactFields : []
797
+ };
798
+ }
799
+ function normaliseExpiresAt(input, maxDays) {
800
+ if (!input) return null;
801
+ const now = Date.now();
802
+ const cap = now + maxDays * 864e5;
803
+ const m = /^([0-9]+)(s|m|h|d)$/i.exec(input);
804
+ if (m) {
805
+ const n = Number(m[1]);
806
+ const unit = m[2].toLowerCase();
807
+ const ms = unit === "s" ? n * 1e3 : unit === "m" ? n * 6e4 : unit === "h" ? n * 36e5 : n * 864e5;
808
+ const at = now + ms;
809
+ if (at > cap) {
810
+ throw makeError(422, "EXPIRY_TOO_LONG", `expiresAt exceeds the object's max of ${maxDays} days`);
811
+ }
812
+ return new Date(at).toISOString();
813
+ }
814
+ const t = Date.parse(input);
815
+ if (Number.isNaN(t)) {
816
+ throw makeError(422, "INVALID_EXPIRY", `expiresAt is not a valid ISO timestamp or duration: ${input}`);
817
+ }
818
+ if (t > cap) {
819
+ throw makeError(422, "EXPIRY_TOO_LONG", `expiresAt exceeds the object's max of ${maxDays} days`);
820
+ }
821
+ if (t <= now) {
822
+ throw makeError(422, "EXPIRY_IN_PAST", "expiresAt must be in the future");
823
+ }
824
+ return new Date(t).toISOString();
825
+ }
826
+ async function defaultHashPassword(password) {
827
+ const g = globalThis;
828
+ const subtle = g.crypto?.subtle;
829
+ const salt = generateToken(16);
830
+ if (!subtle) {
831
+ return `weak$${salt}$${password}`;
832
+ }
833
+ const enc = new TextEncoder();
834
+ const buf = await subtle.digest("SHA-256", enc.encode(salt + ":" + password));
835
+ const hex = Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
836
+ return `sha256$${salt}$${hex}`;
837
+ }
838
+ async function defaultVerifyPassword(password, hash) {
839
+ if (hash.startsWith("weak$")) {
840
+ const [, , stored] = hash.split("$");
841
+ return stored === password;
842
+ }
843
+ if (hash.startsWith("sha256$")) {
844
+ const [, salt, expected] = hash.split("$");
845
+ const g = globalThis;
846
+ const subtle = g.crypto?.subtle;
847
+ if (!subtle) return false;
848
+ const enc = new TextEncoder();
849
+ const buf = await subtle.digest("SHA-256", enc.encode(salt + ":" + password));
850
+ const hex = Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
851
+ return hex === expected;
852
+ }
853
+ return false;
854
+ }
855
+ function makeError(status, code, message) {
856
+ const err = new Error(message);
857
+ err.status = status;
858
+ err.code = code;
859
+ return err;
860
+ }
861
+ var ShareLinkService = class {
862
+ constructor(opts) {
863
+ this.engine = opts.engine;
864
+ this.permissive = opts.permissive ?? false;
865
+ this.hashPassword = opts.hashPassword ?? defaultHashPassword;
866
+ this.verifyPassword = opts.verifyPassword ?? defaultVerifyPassword;
867
+ }
868
+ async createLink(input, context) {
869
+ if (!input.object) throw makeError(400, "VALIDATION_FAILED", "object is required");
870
+ if (!input.recordId) throw makeError(400, "VALIDATION_FAILED", "recordId is required");
871
+ const schema = this.engine.getSchema?.(input.object);
872
+ const policy = getPolicy(schema);
873
+ if (!policy.enabled && !this.permissive && !context.isSystem) {
874
+ throw makeError(
875
+ 422,
876
+ "SHARING_NOT_ENABLED",
877
+ `Object '${input.object}' has not enabled publicSharing in its schema`
878
+ );
879
+ }
880
+ const permission = input.permission ?? "view";
881
+ if (policy.enabled && policy.allowedPermissions.length > 0 && !policy.allowedPermissions.includes(permission)) {
882
+ throw makeError(
883
+ 422,
884
+ "PERMISSION_NOT_ALLOWED",
885
+ `Object '${input.object}' does not allow share permission '${permission}'. Allowed: ${policy.allowedPermissions.join(", ")}`
886
+ );
887
+ }
888
+ const audience = input.audience ?? "link_only";
889
+ if (policy.enabled && policy.allowedAudiences.length > 0 && !policy.allowedAudiences.includes(audience)) {
890
+ throw makeError(
891
+ 422,
892
+ "AUDIENCE_NOT_ALLOWED",
893
+ `Object '${input.object}' does not allow audience '${audience}'. Allowed: ${policy.allowedAudiences.join(", ")}`
894
+ );
895
+ }
896
+ if (audience === "email" && (!input.emailAllowlist || input.emailAllowlist.length === 0)) {
897
+ throw makeError(400, "VALIDATION_FAILED", "emailAllowlist is required when audience=email");
898
+ }
899
+ const exists = await this.engine.find(input.object, {
900
+ where: { id: input.recordId },
901
+ fields: ["id"],
902
+ limit: 1,
903
+ context: SYSTEM_CTX5
904
+ });
905
+ if (!Array.isArray(exists) || exists.length === 0) {
906
+ throw makeError(404, "RECORD_NOT_FOUND", `${input.object}/${input.recordId} does not exist`);
907
+ }
908
+ const maxDays = policy.maxExpiryDays ?? DEFAULT_MAX_EXPIRY_DAYS;
909
+ const expires_at = normaliseExpiresAt(input.expiresAt, maxDays);
910
+ const passwordHash = input.password ? await this.hashPassword(input.password) : null;
911
+ const row = {
912
+ id: `shl_${generateToken(16)}`,
913
+ token: generateToken(TOKEN_LENGTH),
914
+ object_name: input.object,
915
+ record_id: input.recordId,
916
+ permission,
917
+ audience,
918
+ expires_at,
919
+ email_allowlist: input.emailAllowlist && input.emailAllowlist.length > 0 ? input.emailAllowlist.map((e) => e.trim().toLowerCase()).filter(Boolean) : null,
920
+ password_hash: passwordHash,
921
+ redact_fields: input.redactFields && input.redactFields.length > 0 ? input.redactFields : null,
922
+ label: input.label ?? null,
923
+ revoked_at: null,
924
+ created_by: context.userId ?? null,
925
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
926
+ last_used_at: null,
927
+ use_count: 0
928
+ };
929
+ await this.engine.insert("sys_share_link", row, { context: SYSTEM_CTX5 });
930
+ return row;
931
+ }
932
+ async revokeLink(idOrToken, _context) {
933
+ if (!idOrToken) throw makeError(400, "VALIDATION_FAILED", "id or token is required");
934
+ const filter = idOrToken.startsWith("shl_") ? { id: idOrToken } : { token: idOrToken };
935
+ const rows = await this.engine.find("sys_share_link", {
936
+ where: filter,
937
+ fields: ["id", "revoked_at"],
938
+ limit: 1,
939
+ context: SYSTEM_CTX5
940
+ });
941
+ const row = Array.isArray(rows) ? rows[0] : void 0;
942
+ if (!row) return;
943
+ if (row.revoked_at) return;
944
+ await this.engine.update(
945
+ "sys_share_link",
946
+ { id: row.id, revoked_at: (/* @__PURE__ */ new Date()).toISOString() },
947
+ { context: SYSTEM_CTX5 }
948
+ );
949
+ }
950
+ async listLinks(filter, context) {
951
+ const where = {};
952
+ if (filter.object) where.object_name = filter.object;
953
+ if (filter.recordId) where.record_id = filter.recordId;
954
+ if (filter.createdBy) where.created_by = filter.createdBy;
955
+ if (!filter.includeRevoked) where.revoked_at = null;
956
+ const rows = await this.engine.find("sys_share_link", {
957
+ where,
958
+ limit: 200,
959
+ sort: [{ field: "created_at", order: "desc" }],
960
+ context: context.isSystem ? SYSTEM_CTX5 : context
961
+ });
962
+ return Array.isArray(rows) ? rows : [];
963
+ }
964
+ async resolveToken(token, probe = {}) {
965
+ if (!token || typeof token !== "string" || token.length < 8) return null;
966
+ const rows = await this.engine.find("sys_share_link", {
967
+ where: { token },
968
+ limit: 1,
969
+ context: SYSTEM_CTX5
970
+ });
971
+ const row = Array.isArray(rows) ? rows[0] : void 0;
972
+ if (!row) return null;
973
+ if (row.revoked_at) return null;
974
+ if (row.expires_at && Date.parse(row.expires_at) <= Date.now()) return null;
975
+ if (row.audience === "signed_in" && !probe.signedInUserId) return null;
976
+ if (row.audience === "email") {
977
+ const allow = row.email_allowlist ?? [];
978
+ const supplied = (probe.recipientEmail ?? "").trim().toLowerCase();
979
+ if (!supplied || !allow.includes(supplied)) return null;
980
+ }
981
+ if (row.password_hash) {
982
+ if (!probe.providedPassword) return null;
983
+ const ok = await this.verifyPassword(probe.providedPassword, row.password_hash);
984
+ if (!ok) return null;
985
+ }
986
+ const schema = this.engine.getSchema?.(row.object_name);
987
+ const policy = getPolicy(schema);
988
+ const redactFields = Array.from(
989
+ /* @__PURE__ */ new Set([...policy.redactFields ?? [], ...row.redact_fields ?? []])
990
+ );
991
+ try {
992
+ await this.engine.update(
993
+ "sys_share_link",
994
+ {
995
+ id: row.id,
996
+ last_used_at: (/* @__PURE__ */ new Date()).toISOString(),
997
+ use_count: (row.use_count ?? 0) + 1
998
+ },
999
+ { context: SYSTEM_CTX5 }
1000
+ );
1001
+ } catch {
1002
+ }
1003
+ return { link: row, redactFields };
1004
+ }
1005
+ };
1006
+
1007
+ // src/share-link-routes.ts
1008
+ var SYSTEM_CTX6 = { isSystem: true, roles: [], permissions: [] };
1009
+ var defaultContext = (req) => {
1010
+ const header = (name) => {
1011
+ const v = req.headers?.[name];
1012
+ return Array.isArray(v) ? v[0] : v;
1013
+ };
1014
+ return {
1015
+ userId: header("x-user-id"),
1016
+ tenantId: header("x-tenant-id")
1017
+ };
1018
+ };
1019
+ function sendError(res, status, code, message) {
1020
+ res.status(status).json({ error: { code, message } });
1021
+ }
1022
+ function applyRedaction(record, redactFields) {
1023
+ if (!record || typeof record !== "object" || redactFields.length === 0) return record;
1024
+ if (Array.isArray(record)) return record.map((r) => applyRedaction(r, redactFields));
1025
+ const out = {};
1026
+ for (const [k, v] of Object.entries(record)) {
1027
+ if (redactFields.includes(k)) continue;
1028
+ out[k] = v;
1029
+ }
1030
+ return out;
1031
+ }
1032
+ function registerShareLinkRoutes(http, service, engine, opts = {}) {
1033
+ const base = opts.basePath ?? "/api/v1/share-links";
1034
+ const ctxOf = opts.contextFromRequest ?? defaultContext;
1035
+ http.post(base, (async (req, res) => {
1036
+ try {
1037
+ const ctx = ctxOf(req);
1038
+ const body = req.body ?? {};
1039
+ if (!body.object || !body.recordId) {
1040
+ return sendError(res, 400, "VALIDATION_FAILED", "object and recordId are required");
1041
+ }
1042
+ const link = await service.createLink(
1043
+ {
1044
+ object: body.object,
1045
+ recordId: body.recordId,
1046
+ permission: body.permission,
1047
+ audience: body.audience,
1048
+ expiresAt: body.expiresAt ?? null,
1049
+ emailAllowlist: body.emailAllowlist,
1050
+ password: body.password,
1051
+ redactFields: body.redactFields,
1052
+ label: body.label
1053
+ },
1054
+ ctx
1055
+ );
1056
+ await res.status(201).json({ link });
1057
+ } catch (err) {
1058
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to create link");
1059
+ }
1060
+ }));
1061
+ http.get(base, (async (req, res) => {
1062
+ try {
1063
+ const ctx = ctxOf(req);
1064
+ const q = req.query ?? {};
1065
+ const link = await service.listLinks(
1066
+ {
1067
+ object: typeof q.object === "string" ? q.object : void 0,
1068
+ recordId: typeof q.recordId === "string" ? q.recordId : void 0,
1069
+ createdBy: typeof q.createdBy === "string" ? q.createdBy : void 0,
1070
+ includeRevoked: q.includeRevoked === "true" || q.includeRevoked === "1"
1071
+ },
1072
+ ctx
1073
+ );
1074
+ await res.json({ links: link });
1075
+ } catch (err) {
1076
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to list links");
1077
+ }
1078
+ }));
1079
+ http.delete(`${base}/:idOrToken`, (async (req, res) => {
1080
+ try {
1081
+ const ctx = ctxOf(req);
1082
+ await service.revokeLink(req.params.idOrToken, ctx);
1083
+ await res.status(200).json({ ok: true });
1084
+ } catch (err) {
1085
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to revoke link");
1086
+ }
1087
+ }));
1088
+ http.get(`${base}/:token/resolve`, (async (req, res) => {
1089
+ try {
1090
+ const q = req.query ?? {};
1091
+ const signedInUserId = (() => {
1092
+ const v = req.headers?.["x-user-id"];
1093
+ return Array.isArray(v) ? v[0] : v;
1094
+ })();
1095
+ const recipientEmail = typeof q.email === "string" ? q.email : void 0;
1096
+ const providedPassword = typeof q.password === "string" ? q.password : (() => {
1097
+ const v = req.headers?.["x-share-password"];
1098
+ return Array.isArray(v) ? v[0] : v;
1099
+ })();
1100
+ const resolved = await service.resolveToken(req.params.token, {
1101
+ signedInUserId,
1102
+ recipientEmail,
1103
+ providedPassword
1104
+ });
1105
+ if (!resolved) {
1106
+ const probe = await engine.find("sys_share_link", {
1107
+ where: { token: req.params.token },
1108
+ limit: 1,
1109
+ context: SYSTEM_CTX6
1110
+ });
1111
+ const row = Array.isArray(probe) && probe[0] ? probe[0] : null;
1112
+ if (row && !row.revoked_at && (!row.expires_at || Date.parse(row.expires_at) > Date.now())) {
1113
+ if (row.password_hash) {
1114
+ return sendError(
1115
+ res,
1116
+ 401,
1117
+ providedPassword ? "WRONG_PASSWORD" : "NEEDS_PASSWORD",
1118
+ providedPassword ? "Incorrect password" : "This link requires a password"
1119
+ );
1120
+ }
1121
+ if (row.audience === "signed_in" && !signedInUserId) {
1122
+ return sendError(res, 401, "SIGN_IN_REQUIRED", "Please sign in to view this link");
1123
+ }
1124
+ }
1125
+ if (row && (row.revoked_at || row.expires_at && Date.parse(row.expires_at) <= Date.now())) {
1126
+ return sendError(res, 410, "EXPIRED_OR_REVOKED", "Share link has expired or been revoked");
1127
+ }
1128
+ return sendError(res, 404, "INVALID_OR_EXPIRED", "Share link is invalid, expired, or revoked");
1129
+ }
1130
+ const rows = await engine.find(resolved.link.object_name, {
1131
+ where: { id: resolved.link.record_id },
1132
+ limit: 1,
1133
+ context: SYSTEM_CTX6
1134
+ });
1135
+ const record = Array.isArray(rows) && rows[0] ? rows[0] : null;
1136
+ if (!record) {
1137
+ return sendError(res, 410, "RECORD_GONE", "The shared record no longer exists");
1138
+ }
1139
+ await res.json({
1140
+ record: applyRedaction(record, resolved.redactFields),
1141
+ link: {
1142
+ id: resolved.link.id,
1143
+ token: resolved.link.token,
1144
+ object_name: resolved.link.object_name,
1145
+ record_id: resolved.link.record_id,
1146
+ permission: resolved.link.permission,
1147
+ audience: resolved.link.audience,
1148
+ expires_at: resolved.link.expires_at,
1149
+ label: resolved.link.label,
1150
+ created_at: resolved.link.created_at
1151
+ },
1152
+ redactFields: resolved.redactFields
1153
+ });
1154
+ } catch (err) {
1155
+ sendError(res, err?.status ?? 500, err?.code ?? "INTERNAL", err?.message ?? "Failed to resolve link");
1156
+ }
1157
+ }));
1158
+ http.get(`${base}/:token/messages`, (async (req, res) => {
1159
+ try {
1160
+ const password = typeof req.query?.password === "string" ? req.query.password : void 0;
1161
+ const resolved = await service.resolveToken(req.params.token, { providedPassword: password });
1162
+ if (!resolved) {
1163
+ sendError(res, 404, "NOT_FOUND", "Share link not found");
1164
+ return;
1165
+ }
1166
+ if (resolved.link.object_name !== "ai_conversations") {
1167
+ sendError(res, 400, "UNSUPPORTED", "This share link does not expose messages");
1168
+ return;
1169
+ }
1170
+ const SYSTEM_CTX8 = { isSystem: true, roles: [], permissions: [] };
1171
+ const rows = await engine.find("ai_messages", {
1172
+ where: { conversation_id: resolved.link.record_id },
1173
+ sort: [{ field: "created_at", direction: "asc" }],
1174
+ limit: 500,
1175
+ context: SYSTEM_CTX8
1176
+ });
1177
+ res.status(200).json({ data: rows ?? [] });
1178
+ } catch (err) {
1179
+ sendError(
1180
+ res,
1181
+ err?.status ?? 500,
1182
+ err?.code ?? "INTERNAL",
1183
+ err?.message ?? "Failed to load messages"
1184
+ );
1185
+ }
1186
+ }));
1187
+ }
1188
+
1189
+ // src/rule-hooks.ts
1190
+ var SYSTEM_CTX7 = { isSystem: true, roles: [], permissions: [] };
764
1191
  var SHARING_RULE_HOOK_PACKAGE = "plugin-sharing:rules";
765
1192
  function bindRuleHooks(engine, service, rules, logger) {
766
1193
  const objects = /* @__PURE__ */ new Set();
@@ -775,7 +1202,7 @@ function bindRuleHooks(engine, service, rules, logger) {
775
1202
  const data = ctx?.result ?? ctx?.input?.data ?? {};
776
1203
  const id = String(data?.id ?? ctx?.input?.id ?? "");
777
1204
  if (!id) return;
778
- await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX5);
1205
+ await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX7);
779
1206
  } catch (err) {
780
1207
  logger?.warn?.("[sharing-rule] hook evaluation failed", { object: objectName, error: err?.message });
781
1208
  }
@@ -790,7 +1217,7 @@ function unbindAllRuleHooks(engine) {
790
1217
  }
791
1218
 
792
1219
  // src/sharing-plugin.ts
793
- import { SysRecordShare, SysSharingRule } from "@objectstack/platform-objects/security";
1220
+ import { SysRecordShare, SysSharingRule, SysShareLink } from "@objectstack/platform-objects/security";
794
1221
  import { SysDepartment, SysDepartmentMember } from "@objectstack/platform-objects/identity";
795
1222
  var SharingServicePlugin = class {
796
1223
  constructor(options = {}) {
@@ -809,7 +1236,7 @@ var SharingServicePlugin = class {
809
1236
  scope: "system",
810
1237
  defaultDatasource: "cloud",
811
1238
  namespace: "sys",
812
- objects: [SysRecordShare, SysSharingRule, SysDepartment, SysDepartmentMember]
1239
+ objects: [SysRecordShare, SysSharingRule, SysDepartment, SysDepartmentMember, SysShareLink]
813
1240
  });
814
1241
  ctx.logger.info("SharingServicePlugin: schema registered");
815
1242
  }
@@ -861,6 +1288,31 @@ var SharingServicePlugin = class {
861
1288
  } catch (err) {
862
1289
  ctx.logger.warn("SharingServicePlugin: sharing-rule subsystem not started", { error: err?.message });
863
1290
  }
1291
+ try {
1292
+ this.linkService = new ShareLinkService({ engine });
1293
+ ctx.registerService("shareLinks", this.linkService);
1294
+ if (this.options.registerShareLinkRoutes !== false) {
1295
+ let http = null;
1296
+ try {
1297
+ http = ctx.getService("http-server");
1298
+ } catch {
1299
+ }
1300
+ if (http) {
1301
+ registerShareLinkRoutes(http, this.linkService, engine, {
1302
+ basePath: this.options.shareLinkBasePath
1303
+ });
1304
+ ctx.logger.info(
1305
+ "SharingServicePlugin: share-link routes mounted at " + (this.options.shareLinkBasePath ?? "/api/v1/share-links")
1306
+ );
1307
+ } else {
1308
+ ctx.logger.warn(
1309
+ 'SharingServicePlugin: no HTTP server \u2014 share-link REST routes not registered. ShareLinkService is still reachable via kernel.getService("shareLinks").'
1310
+ );
1311
+ }
1312
+ }
1313
+ } catch (err) {
1314
+ ctx.logger.warn("SharingServicePlugin: share-link subsystem not started", { error: err?.message });
1315
+ }
864
1316
  });
865
1317
  }
866
1318
  };
@@ -923,20 +1375,27 @@ function inferTargetId(data, options) {
923
1375
  }
924
1376
  return void 0;
925
1377
  }
1378
+
1379
+ // src/index.ts
1380
+ import { SHARE_LINK_SERVICE } from "@objectstack/spec/contracts";
926
1381
  export {
927
1382
  DepartmentGraphService,
1383
+ SHARE_LINK_SERVICE,
928
1384
  SHARING_RULE_HOOK_PACKAGE,
1385
+ ShareLinkService,
929
1386
  SharingRuleService,
930
1387
  SharingService,
931
1388
  SharingServicePlugin,
932
1389
  SysDepartment2 as SysDepartment,
933
1390
  SysDepartmentMember2 as SysDepartmentMember,
934
1391
  SysRecordShare2 as SysRecordShare,
1392
+ SysShareLink2 as SysShareLink,
935
1393
  SysSharingRule2 as SysSharingRule,
936
1394
  TeamGraphService,
937
1395
  bindRuleHooks,
938
1396
  buildSharingMiddleware,
939
1397
  expandPrincipal,
1398
+ registerShareLinkRoutes,
940
1399
  unbindAllRuleHooks
941
1400
  };
942
1401
  //# sourceMappingURL=index.mjs.map