@columnus/shared 0.1.47
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/LICENSE +21 -0
- package/dist/apiProfile.d.ts +1072 -0
- package/dist/apiProfile.js +4210 -0
- package/dist/apiProfile.js.map +1 -0
- package/dist/apiProfileSlug.d.ts +11 -0
- package/dist/apiProfileSlug.js +39 -0
- package/dist/apiProfileSlug.js.map +1 -0
- package/dist/apiToken.d.ts +6 -0
- package/dist/apiToken.js +35 -0
- package/dist/apiToken.js.map +1 -0
- package/dist/betterAuthSession.d.ts +8 -0
- package/dist/betterAuthSession.js +57 -0
- package/dist/betterAuthSession.js.map +1 -0
- package/dist/bffHost.d.ts +36 -0
- package/dist/bffHost.js +74 -0
- package/dist/bffHost.js.map +1 -0
- package/dist/bffProtocol.d.ts +1357 -0
- package/dist/bffProtocol.js +4362 -0
- package/dist/bffProtocol.js.map +1 -0
- package/dist/bffRedact.d.ts +30 -0
- package/dist/bffRedact.js +55 -0
- package/dist/bffRedact.js.map +1 -0
- package/dist/ciPreviewEnv.d.ts +49 -0
- package/dist/ciPreviewEnv.js +143 -0
- package/dist/ciPreviewEnv.js.map +1 -0
- package/dist/clipboardTransfer.d.ts +25 -0
- package/dist/clipboardTransfer.js +4862 -0
- package/dist/clipboardTransfer.js.map +1 -0
- package/dist/convexSiteUrl.d.ts +73 -0
- package/dist/convexSiteUrl.js +137 -0
- package/dist/convexSiteUrl.js.map +1 -0
- package/dist/csv.d.ts +54 -0
- package/dist/csv.js +5499 -0
- package/dist/csv.js.map +1 -0
- package/dist/dataTransfer.d.ts +21 -0
- package/dist/dataTransfer.js +74 -0
- package/dist/dataTransfer.js.map +1 -0
- package/dist/dateValues.d.ts +18 -0
- package/dist/dateValues.js +73 -0
- package/dist/dateValues.js.map +1 -0
- package/dist/deployOutput.d.ts +3 -0
- package/dist/deployOutput.js +23 -0
- package/dist/deployOutput.js.map +1 -0
- package/dist/displayLabel.d.ts +13 -0
- package/dist/displayLabel.js +4342 -0
- package/dist/displayLabel.js.map +1 -0
- package/dist/displayTemplateGenerator.d.ts +11 -0
- package/dist/displayTemplateGenerator.js +98 -0
- package/dist/displayTemplateGenerator.js.map +1 -0
- package/dist/displayTypes.d.ts +10 -0
- package/dist/displayTypes.js +3 -0
- package/dist/displayTypes.js.map +1 -0
- package/dist/dotenv.d.ts +8 -0
- package/dist/dotenv.js +61 -0
- package/dist/dotenv.js.map +1 -0
- package/dist/erdLayout.d.ts +27 -0
- package/dist/erdLayout.js +78 -0
- package/dist/erdLayout.js.map +1 -0
- package/dist/fieldTypeFormat.d.ts +5 -0
- package/dist/fieldTypeFormat.js +63 -0
- package/dist/fieldTypeFormat.js.map +1 -0
- package/dist/fieldTypeMeta.d.ts +50 -0
- package/dist/fieldTypeMeta.js +84 -0
- package/dist/fieldTypeMeta.js.map +1 -0
- package/dist/fieldValueEqual.d.ts +4 -0
- package/dist/fieldValueEqual.js +58 -0
- package/dist/fieldValueEqual.js.map +1 -0
- package/dist/importMatch.d.ts +80 -0
- package/dist/importMatch.js +5643 -0
- package/dist/importMatch.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +10647 -0
- package/dist/index.js.map +1 -0
- package/dist/inferIdentifier.d.ts +18 -0
- package/dist/inferIdentifier.js +49 -0
- package/dist/inferIdentifier.js.map +1 -0
- package/dist/keyboard.d.ts +16 -0
- package/dist/keyboard.js +71 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/manifestDeploy.d.ts +27 -0
- package/dist/manifestDeploy.js +16 -0
- package/dist/manifestDeploy.js.map +1 -0
- package/dist/manifestHash.d.ts +11 -0
- package/dist/manifestHash.js +35 -0
- package/dist/manifestHash.js.map +1 -0
- package/dist/previewDeployName.d.ts +26 -0
- package/dist/previewDeployName.js +77 -0
- package/dist/previewDeployName.js.map +1 -0
- package/dist/proposePolicy.d.ts +17 -0
- package/dist/proposePolicy.js +33 -0
- package/dist/proposePolicy.js.map +1 -0
- package/dist/queryBetween.d.ts +20 -0
- package/dist/queryBetween.js +28 -0
- package/dist/queryBetween.js.map +1 -0
- package/dist/queryExecution.d.ts +5 -0
- package/dist/queryExecution.js +58 -0
- package/dist/queryExecution.js.map +1 -0
- package/dist/queryFieldOps.d.ts +66 -0
- package/dist/queryFieldOps.js +138 -0
- package/dist/queryFieldOps.js.map +1 -0
- package/dist/queryMerge.d.ts +29 -0
- package/dist/queryMerge.js +4277 -0
- package/dist/queryMerge.js.map +1 -0
- package/dist/queryPlan.d.ts +97 -0
- package/dist/queryPlan.js +4564 -0
- package/dist/queryPlan.js.map +1 -0
- package/dist/queryStudio.d.ts +25 -0
- package/dist/queryStudio.js +4658 -0
- package/dist/queryStudio.js.map +1 -0
- package/dist/queryTypes.d.ts +402 -0
- package/dist/queryTypes.js +4239 -0
- package/dist/queryTypes.js.map +1 -0
- package/dist/queryValidators.d.ts +99 -0
- package/dist/queryValidators.js +76 -0
- package/dist/queryValidators.js.map +1 -0
- package/dist/readApiQuery.d.ts +10 -0
- package/dist/readApiQuery.js +24 -0
- package/dist/readApiQuery.js.map +1 -0
- package/dist/recordTableColumns.d.ts +21 -0
- package/dist/recordTableColumns.js +4469 -0
- package/dist/recordTableColumns.js.map +1 -0
- package/dist/refDisplayRequests.d.ts +17 -0
- package/dist/refDisplayRequests.js +4877 -0
- package/dist/refDisplayRequests.js.map +1 -0
- package/dist/refGraph.d.ts +23 -0
- package/dist/refGraph.js +102 -0
- package/dist/refGraph.js.map +1 -0
- package/dist/refsRecompute.d.ts +8 -0
- package/dist/refsRecompute.js +12 -0
- package/dist/refsRecompute.js.map +1 -0
- package/dist/richDoc.d.ts +67 -0
- package/dist/richDoc.js +4408 -0
- package/dist/richDoc.js.map +1 -0
- package/dist/schemaGenerator.d.ts +31 -0
- package/dist/schemaGenerator.js +198 -0
- package/dist/schemaGenerator.js.map +1 -0
- package/dist/schemaShrink.d.ts +74 -0
- package/dist/schemaShrink.js +132 -0
- package/dist/schemaShrink.js.map +1 -0
- package/dist/schemaShrinkValidators.d.ts +72 -0
- package/dist/schemaShrinkValidators.js +31 -0
- package/dist/schemaShrinkValidators.js.map +1 -0
- package/dist/schemaTypes.d.ts +454 -0
- package/dist/schemaTypes.js +4557 -0
- package/dist/schemaTypes.js.map +1 -0
- package/dist/share.d.ts +8 -0
- package/dist/share.js +4755 -0
- package/dist/share.js.map +1 -0
- package/dist/shareApiBridge.d.ts +18 -0
- package/dist/shareApiBridge.js +4283 -0
- package/dist/shareApiBridge.js.map +1 -0
- package/dist/shareGuestPolicy.d.ts +14 -0
- package/dist/shareGuestPolicy.js +90 -0
- package/dist/shareGuestPolicy.js.map +1 -0
- package/dist/shareProposal.d.ts +113 -0
- package/dist/shareProposal.js +4810 -0
- package/dist/shareProposal.js.map +1 -0
- package/dist/shareProposalDiff.d.ts +13 -0
- package/dist/shareProposalDiff.js +91 -0
- package/dist/shareProposalDiff.js.map +1 -0
- package/dist/shareScopes.d.ts +14 -0
- package/dist/shareScopes.js +223 -0
- package/dist/shareScopes.js.map +1 -0
- package/dist/shareTokenCrypto.d.ts +9 -0
- package/dist/shareTokenCrypto.js +87 -0
- package/dist/shareTokenCrypto.js.map +1 -0
- package/dist/shareTokens.d.ts +6 -0
- package/dist/shareTokens.js +26 -0
- package/dist/shareTokens.js.map +1 -0
- package/dist/shareTypes.d.ts +107 -0
- package/dist/shareTypes.js +3 -0
- package/dist/shareTypes.js.map +1 -0
- package/dist/shareValidators.d.ts +168 -0
- package/dist/shareValidators.js +124 -0
- package/dist/shareValidators.js.map +1 -0
- package/dist/shareWizard.d.ts +14 -0
- package/dist/shareWizard.js +200 -0
- package/dist/shareWizard.js.map +1 -0
- package/dist/spaceBundle.d.ts +67 -0
- package/dist/spaceBundle.js +4664 -0
- package/dist/spaceBundle.js.map +1 -0
- package/dist/storageFieldValues.d.ts +10 -0
- package/dist/storageFieldValues.js +4421 -0
- package/dist/storageFieldValues.js.map +1 -0
- package/dist/studioBoot.d.ts +49 -0
- package/dist/studioBoot.js +167 -0
- package/dist/studioBoot.js.map +1 -0
- package/dist/syncClampMembership.d.ts +22 -0
- package/dist/syncClampMembership.js +74 -0
- package/dist/syncClampMembership.js.map +1 -0
- package/dist/tenantHosts.d.ts +30 -0
- package/dist/tenantHosts.js +60 -0
- package/dist/tenantHosts.js.map +1 -0
- package/dist/tenantSecretsCrypto.d.ts +31 -0
- package/dist/tenantSecretsCrypto.js +145 -0
- package/dist/tenantSecretsCrypto.js.map +1 -0
- package/dist/useDebouncedCommit.d.ts +11 -0
- package/dist/useDebouncedCommit.js +34 -0
- package/dist/useDebouncedCommit.js.map +1 -0
- package/dist/useDebouncedValue.d.ts +1 -0
- package/dist/useDebouncedValue.js +15 -0
- package/dist/useDebouncedValue.js.map +1 -0
- package/dist/useModifierHeld.d.ts +2 -0
- package/dist/useModifierHeld.js +28 -0
- package/dist/useModifierHeld.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/tenantSecretsCrypto.ts
|
|
2
|
+
var TENANT_SECRETS_KEK_ENV = "TENANT_SECRETS_KEK";
|
|
3
|
+
var TENANT_SECRET_CRYPTO_VERSION = 1;
|
|
4
|
+
var IV_BYTES = 12;
|
|
5
|
+
var KEY_BYTES = 32;
|
|
6
|
+
function base64UrlEncode(bytes) {
|
|
7
|
+
let binary = "";
|
|
8
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
9
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
10
|
+
}
|
|
11
|
+
function base64UrlDecode(value) {
|
|
12
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
13
|
+
const padLen = (4 - padded.length % 4) % 4;
|
|
14
|
+
const binary = atob(padded + "=".repeat(padLen));
|
|
15
|
+
const out = new Uint8Array(binary.length);
|
|
16
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function hexDecode(hex) {
|
|
20
|
+
if (hex.length % 2 !== 0) {
|
|
21
|
+
throw new Error("TENANT_SECRETS_KEK hex must have even length");
|
|
22
|
+
}
|
|
23
|
+
const out = new Uint8Array(hex.length / 2);
|
|
24
|
+
for (let i = 0; i < out.length; i++) {
|
|
25
|
+
const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
26
|
+
if (Number.isNaN(byte)) {
|
|
27
|
+
throw new Error("TENANT_SECRETS_KEK hex contains non-hex characters");
|
|
28
|
+
}
|
|
29
|
+
out[i] = byte;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function parseTenantSecretsKek(raw) {
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (!trimmed) {
|
|
36
|
+
throw new Error(`${TENANT_SECRETS_KEK_ENV} is empty`);
|
|
37
|
+
}
|
|
38
|
+
let bytes;
|
|
39
|
+
if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
|
|
40
|
+
bytes = hexDecode(trimmed);
|
|
41
|
+
} else {
|
|
42
|
+
try {
|
|
43
|
+
bytes = base64UrlDecode(trimmed);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`${TENANT_SECRETS_KEK_ENV} must be 32-byte key as base64/base64url or 64-char hex`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (bytes.byteLength !== KEY_BYTES) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`${TENANT_SECRETS_KEK_ENV} must decode to exactly ${KEY_BYTES} bytes (got ${bytes.byteLength})`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return bytes;
|
|
56
|
+
}
|
|
57
|
+
function readTenantSecretsKekFromEnv(env) {
|
|
58
|
+
const raw = env[TENANT_SECRETS_KEK_ENV];
|
|
59
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
60
|
+
throw new Error(`${TENANT_SECRETS_KEK_ENV} is not set`);
|
|
61
|
+
}
|
|
62
|
+
return parseTenantSecretsKek(raw);
|
|
63
|
+
}
|
|
64
|
+
function toArrayBufferView(bytes) {
|
|
65
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
66
|
+
copy.set(bytes);
|
|
67
|
+
return copy;
|
|
68
|
+
}
|
|
69
|
+
async function importAesKey(kek) {
|
|
70
|
+
if (kek.byteLength !== KEY_BYTES) {
|
|
71
|
+
throw new Error(`AES-256 KEK must be ${KEY_BYTES} bytes`);
|
|
72
|
+
}
|
|
73
|
+
return crypto.subtle.importKey("raw", toArrayBufferView(kek), { name: "AES-GCM" }, false, [
|
|
74
|
+
"encrypt",
|
|
75
|
+
"decrypt"
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
async function encryptTenantSecret(plaintext, kek) {
|
|
79
|
+
const trimmed = plaintext.trim();
|
|
80
|
+
if (!trimmed) {
|
|
81
|
+
throw new Error("Refusing to encrypt empty secret");
|
|
82
|
+
}
|
|
83
|
+
const key = await importAesKey(kek);
|
|
84
|
+
const iv = new Uint8Array(IV_BYTES);
|
|
85
|
+
crypto.getRandomValues(iv);
|
|
86
|
+
const encrypted = new Uint8Array(
|
|
87
|
+
await crypto.subtle.encrypt(
|
|
88
|
+
{ name: "AES-GCM", iv: toArrayBufferView(iv) },
|
|
89
|
+
key,
|
|
90
|
+
new TextEncoder().encode(trimmed)
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
const packed = new Uint8Array(1 + IV_BYTES + encrypted.byteLength);
|
|
94
|
+
packed[0] = TENANT_SECRET_CRYPTO_VERSION;
|
|
95
|
+
packed.set(iv, 1);
|
|
96
|
+
packed.set(encrypted, 1 + IV_BYTES);
|
|
97
|
+
return {
|
|
98
|
+
ciphertext: base64UrlEncode(packed),
|
|
99
|
+
keyVersion: TENANT_SECRET_CRYPTO_VERSION
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async function decryptTenantSecret(ciphertext, kek) {
|
|
103
|
+
const trimmed = ciphertext.trim();
|
|
104
|
+
if (!trimmed) {
|
|
105
|
+
throw new Error("Ciphertext is empty");
|
|
106
|
+
}
|
|
107
|
+
let packed;
|
|
108
|
+
try {
|
|
109
|
+
packed = base64UrlDecode(trimmed);
|
|
110
|
+
} catch {
|
|
111
|
+
throw new Error("Ciphertext is not valid base64url");
|
|
112
|
+
}
|
|
113
|
+
if (packed.byteLength < 1 + IV_BYTES + 16) {
|
|
114
|
+
throw new Error("Ciphertext is too short");
|
|
115
|
+
}
|
|
116
|
+
const version = packed[0];
|
|
117
|
+
if (version !== TENANT_SECRET_CRYPTO_VERSION) {
|
|
118
|
+
throw new Error(`Unsupported tenant secret crypto version: ${version}`);
|
|
119
|
+
}
|
|
120
|
+
const iv = packed.subarray(1, 1 + IV_BYTES);
|
|
121
|
+
const data = packed.subarray(1 + IV_BYTES);
|
|
122
|
+
const key = await importAesKey(kek);
|
|
123
|
+
let decrypted;
|
|
124
|
+
try {
|
|
125
|
+
decrypted = await crypto.subtle.decrypt(
|
|
126
|
+
{ name: "AES-GCM", iv: toArrayBufferView(iv) },
|
|
127
|
+
key,
|
|
128
|
+
toArrayBufferView(data)
|
|
129
|
+
);
|
|
130
|
+
} catch {
|
|
131
|
+
throw new Error("Failed to decrypt tenant secret (wrong key or corrupt ciphertext)");
|
|
132
|
+
}
|
|
133
|
+
return new TextDecoder().decode(decrypted);
|
|
134
|
+
}
|
|
135
|
+
function generateTenantSecretsKekBase64() {
|
|
136
|
+
const bytes = new Uint8Array(KEY_BYTES);
|
|
137
|
+
crypto.getRandomValues(bytes);
|
|
138
|
+
let binary = "";
|
|
139
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
140
|
+
return btoa(binary);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { TENANT_SECRETS_KEK_ENV, TENANT_SECRET_CRYPTO_VERSION, decryptTenantSecret, encryptTenantSecret, generateTenantSecretsKekBase64, parseTenantSecretsKek, readTenantSecretsKekFromEnv };
|
|
144
|
+
//# sourceMappingURL=tenantSecretsCrypto.js.map
|
|
145
|
+
//# sourceMappingURL=tenantSecretsCrypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tenantSecretsCrypto.ts"],"names":[],"mappings":"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","file":"tenantSecretsCrypto.js","sourcesContent":["/**\n * App-level encryption for management-plane tenant secrets (deploy keys).\n * AES-256-GCM, random 12-byte IV, version byte prefix in the ciphertext blob.\n *\n * KEK: set `TENANT_SECRETS_KEK` on the management Convex deployment to 32 raw\n * bytes encoded as standard/base64url base64 or 64-char hex\n * (`openssl rand -base64 32`).\n */\n\nexport const TENANT_SECRETS_KEK_ENV = \"TENANT_SECRETS_KEK\";\n\n/** Ciphertext format / algorithm version (stored as first byte + `keyVersion`). */\nexport const TENANT_SECRET_CRYPTO_VERSION = 1;\n\nconst IV_BYTES = 12;\nconst KEY_BYTES = 32;\n\nexport type EncryptedTenantSecret = {\n /** base64url(version || iv || ciphertext+tag) */\n ciphertext: string;\n keyVersion: number;\n};\n\ntype ProcessEnvLike = Record<string, string | undefined>;\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(value: string): Uint8Array {\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padLen = (4 - (padded.length % 4)) % 4;\n const binary = atob(padded + \"=\".repeat(padLen));\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\nfunction hexDecode(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) {\n throw new Error(\"TENANT_SECRETS_KEK hex must have even length\");\n }\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n if (Number.isNaN(byte)) {\n throw new Error(\"TENANT_SECRETS_KEK hex contains non-hex characters\");\n }\n out[i] = byte;\n }\n return out;\n}\n\n/** Parse a 32-byte KEK from base64, base64url, or hex. */\nexport function parseTenantSecretsKek(raw: string): Uint8Array {\n const trimmed = raw.trim();\n if (!trimmed) {\n throw new Error(`${TENANT_SECRETS_KEK_ENV} is empty`);\n }\n\n let bytes: Uint8Array;\n if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {\n bytes = hexDecode(trimmed);\n } else {\n try {\n bytes = base64UrlDecode(trimmed);\n } catch {\n throw new Error(\n `${TENANT_SECRETS_KEK_ENV} must be 32-byte key as base64/base64url or 64-char hex`,\n );\n }\n }\n\n if (bytes.byteLength !== KEY_BYTES) {\n throw new Error(\n `${TENANT_SECRETS_KEK_ENV} must decode to exactly ${KEY_BYTES} bytes (got ${bytes.byteLength})`,\n );\n }\n return bytes;\n}\n\nexport function readTenantSecretsKekFromEnv(env: ProcessEnvLike): Uint8Array {\n const raw = env[TENANT_SECRETS_KEK_ENV];\n if (raw === undefined || raw.trim() === \"\") {\n throw new Error(`${TENANT_SECRETS_KEK_ENV} is not set`);\n }\n return parseTenantSecretsKek(raw);\n}\n\n/** Copy into a standalone buffer so Web Crypto accepts the view under strict DOM typings. */\nfunction toArrayBufferView(bytes: Uint8Array): Uint8Array<ArrayBuffer> {\n const copy = new Uint8Array(bytes.byteLength);\n copy.set(bytes);\n return copy;\n}\n\nasync function importAesKey(kek: Uint8Array) {\n if (kek.byteLength !== KEY_BYTES) {\n throw new Error(`AES-256 KEK must be ${KEY_BYTES} bytes`);\n }\n return crypto.subtle.importKey(\"raw\", toArrayBufferView(kek), { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Encrypt a tenant secret (e.g. Convex deploy key).\n * Returns opaque ciphertext for `tenant_secrets.convexDeployKeyCiphertext`\n * and `keyVersion` for the schema column.\n */\nexport async function encryptTenantSecret(\n plaintext: string,\n kek: Uint8Array,\n): Promise<EncryptedTenantSecret> {\n const trimmed = plaintext.trim();\n if (!trimmed) {\n throw new Error(\"Refusing to encrypt empty secret\");\n }\n\n const key = await importAesKey(kek);\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n\n const encrypted = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBufferView(iv) },\n key,\n new TextEncoder().encode(trimmed),\n ),\n );\n\n const packed = new Uint8Array(1 + IV_BYTES + encrypted.byteLength);\n packed[0] = TENANT_SECRET_CRYPTO_VERSION;\n packed.set(iv, 1);\n packed.set(encrypted, 1 + IV_BYTES);\n\n return {\n ciphertext: base64UrlEncode(packed),\n keyVersion: TENANT_SECRET_CRYPTO_VERSION,\n };\n}\n\n/** Decrypt a blob produced by {@link encryptTenantSecret}. */\nexport async function decryptTenantSecret(ciphertext: string, kek: Uint8Array): Promise<string> {\n const trimmed = ciphertext.trim();\n if (!trimmed) {\n throw new Error(\"Ciphertext is empty\");\n }\n\n let packed: Uint8Array;\n try {\n packed = base64UrlDecode(trimmed);\n } catch {\n throw new Error(\"Ciphertext is not valid base64url\");\n }\n\n if (packed.byteLength < 1 + IV_BYTES + 16) {\n throw new Error(\"Ciphertext is too short\");\n }\n\n const version = packed[0];\n if (version !== TENANT_SECRET_CRYPTO_VERSION) {\n throw new Error(`Unsupported tenant secret crypto version: ${version}`);\n }\n\n const iv = packed.subarray(1, 1 + IV_BYTES);\n const data = packed.subarray(1 + IV_BYTES);\n const key = await importAesKey(kek);\n\n let decrypted: ArrayBuffer;\n try {\n decrypted = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: toArrayBufferView(iv) },\n key,\n toArrayBufferView(data),\n );\n } catch {\n throw new Error(\"Failed to decrypt tenant secret (wrong key or corrupt ciphertext)\");\n }\n\n return new TextDecoder().decode(decrypted);\n}\n\n/** Generate a new KEK suitable for `TENANT_SECRETS_KEK` (base64). */\nexport function generateTenantSecretsKekBase64(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Options<T> = {
|
|
2
|
+
delay?: number;
|
|
3
|
+
equals?: (a: T, b: T) => boolean;
|
|
4
|
+
};
|
|
5
|
+
/** Local draft with debounced commit; `resetDraft` cancels pending timers and commits immediately. */
|
|
6
|
+
export declare function useDebouncedCommit<T>(applied: T, onCommit: (value: T) => void, options?: Options<T>): {
|
|
7
|
+
draft: T;
|
|
8
|
+
setDraft: import("react").Dispatch<import("react").SetStateAction<T>>;
|
|
9
|
+
resetDraft: (value: T) => void;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
// packages/@columnus/shared/src/useDebouncedCommit.ts
|
|
4
|
+
function useDebouncedCommit(applied, onCommit, options) {
|
|
5
|
+
const delay = options?.delay ?? 300;
|
|
6
|
+
const equals = options?.equals ?? ((a, b) => a === b);
|
|
7
|
+
const [draft, setDraft] = useState(applied);
|
|
8
|
+
const generationRef = useRef(0);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setDraft(applied);
|
|
11
|
+
}, [applied]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (equals(draft, applied)) return;
|
|
14
|
+
const generation = generationRef.current;
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
if (generation !== generationRef.current) return;
|
|
17
|
+
onCommit(draft);
|
|
18
|
+
}, delay);
|
|
19
|
+
return () => clearTimeout(timer);
|
|
20
|
+
}, [draft, applied, onCommit, delay, equals]);
|
|
21
|
+
const resetDraft = useCallback(
|
|
22
|
+
(value) => {
|
|
23
|
+
generationRef.current += 1;
|
|
24
|
+
setDraft(value);
|
|
25
|
+
if (!equals(value, applied)) onCommit(value);
|
|
26
|
+
},
|
|
27
|
+
[applied, onCommit, equals]
|
|
28
|
+
);
|
|
29
|
+
return { draft, setDraft, resetDraft };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { useDebouncedCommit };
|
|
33
|
+
//# sourceMappingURL=useDebouncedCommit.js.map
|
|
34
|
+
//# sourceMappingURL=useDebouncedCommit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useDebouncedCommit.ts"],"names":[],"mappings":";;;AAQO,SAAS,kBAAA,CACd,OAAA,EACA,QAAA,EACA,OAAA,EACA;AACA,EAAA,MAAM,KAAA,GAAQ,SAAS,KAAA,IAAS,GAAA;AAChC,EAAA,MAAM,SAAS,OAAA,EAAS,MAAA,KAAW,CAAC,CAAA,EAAG,MAAM,CAAA,KAAM,CAAA,CAAA;AACnD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1C,EAAA,MAAM,aAAA,GAAgB,OAAO,CAAC,CAAA;AAE9B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,QAAA,CAAS,OAAO,CAAA;AAAA,EAClB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,CAAO,KAAA,EAAO,OAAO,CAAA,EAAG;AAC5B,IAAA,MAAM,aAAa,aAAA,CAAc,OAAA;AACjC,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,IAAI,UAAA,KAAe,cAAc,OAAA,EAAS;AAC1C,MAAA,QAAA,CAAS,KAAK,CAAA;AAAA,IAChB,GAAG,KAAK,CAAA;AACR,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,GAAG,CAAC,KAAA,EAAO,SAAS,QAAA,EAAU,KAAA,EAAO,MAAM,CAAC,CAAA;AAE5C,EAAA,MAAM,UAAA,GAAa,WAAA;AAAA,IACjB,CAAC,KAAA,KAAa;AACZ,MAAA,aAAA,CAAc,OAAA,IAAW,CAAA;AACzB,MAAA,QAAA,CAAS,KAAK,CAAA;AACd,MAAA,IAAI,CAAC,MAAA,CAAO,KAAA,EAAO,OAAO,CAAA,WAAY,KAAK,CAAA;AAAA,IAC7C,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,QAAA,EAAU,MAAM;AAAA,GAC5B;AAEA,EAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,UAAA,EAAW;AACvC","file":"useDebouncedCommit.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\ntype Options<T> = {\n delay?: number;\n equals?: (a: T, b: T) => boolean;\n};\n\n/** Local draft with debounced commit; `resetDraft` cancels pending timers and commits immediately. */\nexport function useDebouncedCommit<T>(\n applied: T,\n onCommit: (value: T) => void,\n options?: Options<T>,\n) {\n const delay = options?.delay ?? 300;\n const equals = options?.equals ?? ((a, b) => a === b);\n const [draft, setDraft] = useState(applied);\n const generationRef = useRef(0);\n\n useEffect(() => {\n setDraft(applied);\n }, [applied]);\n\n useEffect(() => {\n if (equals(draft, applied)) return;\n const generation = generationRef.current;\n const timer = setTimeout(() => {\n if (generation !== generationRef.current) return;\n onCommit(draft);\n }, delay);\n return () => clearTimeout(timer);\n }, [draft, applied, onCommit, delay, equals]);\n\n const resetDraft = useCallback(\n (value: T) => {\n generationRef.current += 1;\n setDraft(value);\n if (!equals(value, applied)) onCommit(value);\n },\n [applied, onCommit, equals],\n );\n\n return { draft, setDraft, resetDraft };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useDebouncedValue<T>(value: T, ms: number): T;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// packages/@columnus/shared/src/useDebouncedValue.ts
|
|
4
|
+
function useDebouncedValue(value, ms) {
|
|
5
|
+
const [debounced, setDebounced] = useState(value);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timer = setTimeout(() => setDebounced(value), ms);
|
|
8
|
+
return () => clearTimeout(timer);
|
|
9
|
+
}, [value, ms]);
|
|
10
|
+
return debounced;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { useDebouncedValue };
|
|
14
|
+
//# sourceMappingURL=useDebouncedValue.js.map
|
|
15
|
+
//# sourceMappingURL=useDebouncedValue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useDebouncedValue.ts"],"names":[],"mappings":";;;AAEO,SAAS,iBAAA,CAAqB,OAAU,EAAA,EAAe;AAC5D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAChD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,YAAA,CAAa,KAAK,GAAG,EAAE,CAAA;AACtD,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,KAAA,EAAO,EAAE,CAAC,CAAA;AACd,EAAA,OAAO,SAAA;AACT","file":"useDebouncedValue.js","sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport function useDebouncedValue<T>(value: T, ms: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), ms);\n return () => clearTimeout(timer);\n }, [value, ms]);\n return debounced;\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// packages/@columnus/shared/src/useModifierHeld.ts
|
|
4
|
+
function useModifierHeld() {
|
|
5
|
+
const [held, setHeld] = useState(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const onKeyDown = (e) => {
|
|
8
|
+
if (e.metaKey || e.ctrlKey) setHeld(true);
|
|
9
|
+
};
|
|
10
|
+
const onKeyUp = (e) => {
|
|
11
|
+
if (!e.metaKey && !e.ctrlKey) setHeld(false);
|
|
12
|
+
};
|
|
13
|
+
const onBlur = () => setHeld(false);
|
|
14
|
+
window.addEventListener("keydown", onKeyDown);
|
|
15
|
+
window.addEventListener("keyup", onKeyUp);
|
|
16
|
+
window.addEventListener("blur", onBlur);
|
|
17
|
+
return () => {
|
|
18
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
19
|
+
window.removeEventListener("keyup", onKeyUp);
|
|
20
|
+
window.removeEventListener("blur", onBlur);
|
|
21
|
+
};
|
|
22
|
+
}, []);
|
|
23
|
+
return held;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { useModifierHeld };
|
|
27
|
+
//# sourceMappingURL=useModifierHeld.js.map
|
|
28
|
+
//# sourceMappingURL=useModifierHeld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useModifierHeld.ts"],"names":[],"mappings":";;;AAGO,SAAS,eAAA,GAA2B;AACzC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AAEtC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAqB;AACtC,MAAA,IAAI,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,UAAiB,IAAI,CAAA;AAAA,IAC1C,CAAA;AACA,IAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAAqB;AACpC,MAAA,IAAI,CAAC,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA,CAAE,OAAA,UAAiB,KAAK,CAAA;AAAA,IAC7C,CAAA;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,KAAK,CAAA;AAElC,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC5C,IAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,OAAO,CAAA;AACxC,IAAA,MAAA,CAAO,gBAAA,CAAiB,QAAQ,MAAM,CAAA;AACtC,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAC/C,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC3C,MAAA,MAAA,CAAO,mBAAA,CAAoB,QAAQ,MAAM,CAAA;AAAA,IAC3C,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,IAAA;AACT","file":"useModifierHeld.js","sourcesContent":["import { useEffect, useState } from \"react\";\n\n/** True while ⌘ (Mac) or Ctrl (Windows/Linux) is held. */\nexport function useModifierHeld(): boolean {\n const [held, setHeld] = useState(false);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.metaKey || e.ctrlKey) setHeld(true);\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (!e.metaKey && !e.ctrlKey) setHeld(false);\n };\n const onBlur = () => setHeld(false);\n\n window.addEventListener(\"keydown\", onKeyDown);\n window.addEventListener(\"keyup\", onKeyUp);\n window.addEventListener(\"blur\", onBlur);\n return () => {\n window.removeEventListener(\"keydown\", onKeyDown);\n window.removeEventListener(\"keyup\", onKeyUp);\n window.removeEventListener(\"blur\", onBlur);\n };\n }, []);\n\n return held;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@columnus/shared",
|
|
3
|
+
"version": "0.1.47",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Columnus shared package (CMS install family)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/movement-overdrive/convex-studio.git",
|
|
11
|
+
"directory": "packages/@columnus/shared"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/movement-overdrive/convex-studio/tree/main/packages/@columnus/shared",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/movement-overdrive/convex-studio/issues"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./dist/index.js",
|
|
22
|
+
"./*": "./dist/*.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"papaparse": "^5.5.4",
|
|
26
|
+
"zod": "^3.25.76"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": "^19.0.0",
|
|
30
|
+
"react-dom": "^19.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|