@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,18 @@
|
|
|
1
|
+
/** Whether `name` is a valid manifest table/field identifier. */
|
|
2
|
+
export declare function isValidIdentifier(name: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Infer a camelCase identifier from human display text.
|
|
5
|
+
* Does not pluralize/singularize — splits on non-alphanumeric runs only.
|
|
6
|
+
*/
|
|
7
|
+
export declare function inferIdentifier(displayName: string): string;
|
|
8
|
+
/** True when `name` appears in `siblings` (exact match). */
|
|
9
|
+
export declare function hasIdentifierCollision(name: string, siblings: Iterable<string>): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Return `base` or `base2`, `base3`, … when `base` is already taken.
|
|
12
|
+
* Empty `base` is returned unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export declare function uniqueIdentifier(base: string, reservedNames?: Iterable<string>): string;
|
|
15
|
+
/** Infer an identifier from display text, bumping with a numeric suffix when taken. */
|
|
16
|
+
export declare function inferUniqueIdentifier(displayName: string, reservedNames?: Iterable<string>): string;
|
|
17
|
+
/** First duplicate identifier in a list, if any. */
|
|
18
|
+
export declare function findDuplicateIdentifier(names: readonly string[]): string | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/inferIdentifier.ts
|
|
2
|
+
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
+
function isValidIdentifier(name) {
|
|
4
|
+
return IDENT.test(name);
|
|
5
|
+
}
|
|
6
|
+
function inferIdentifier(displayName) {
|
|
7
|
+
const segments = displayName.trim().split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
8
|
+
if (segments.length === 0) return "";
|
|
9
|
+
const parts = segments.map((segment, index) => {
|
|
10
|
+
if (index === 0) return segment.toLowerCase();
|
|
11
|
+
return segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase();
|
|
12
|
+
});
|
|
13
|
+
let result = parts.join("").replace(/[^A-Za-z0-9_]/g, "");
|
|
14
|
+
if (!result) return "";
|
|
15
|
+
if (/^[0-9]/.test(result)) result = `n${result}`;
|
|
16
|
+
if (!IDENT.test(result)) return "";
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
function hasIdentifierCollision(name, siblings) {
|
|
20
|
+
for (const sibling of siblings) {
|
|
21
|
+
if (sibling === name) return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function uniqueIdentifier(base, reservedNames = []) {
|
|
26
|
+
if (!base) return base;
|
|
27
|
+
const used = new Set(reservedNames);
|
|
28
|
+
if (!used.has(base)) return base;
|
|
29
|
+
let n = 2;
|
|
30
|
+
while (used.has(`${base}${n}`)) n++;
|
|
31
|
+
return `${base}${n}`;
|
|
32
|
+
}
|
|
33
|
+
function inferUniqueIdentifier(displayName, reservedNames = []) {
|
|
34
|
+
const base = inferIdentifier(displayName);
|
|
35
|
+
if (!base) return "";
|
|
36
|
+
return uniqueIdentifier(base, reservedNames);
|
|
37
|
+
}
|
|
38
|
+
function findDuplicateIdentifier(names) {
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const name of names) {
|
|
41
|
+
if (seen.has(name)) return name;
|
|
42
|
+
seen.add(name);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { findDuplicateIdentifier, hasIdentifierCollision, inferIdentifier, inferUniqueIdentifier, isValidIdentifier, uniqueIdentifier };
|
|
48
|
+
//# sourceMappingURL=inferIdentifier.js.map
|
|
49
|
+
//# sourceMappingURL=inferIdentifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/inferIdentifier.ts"],"names":[],"mappings":";AAAA,IAAM,KAAA,GAAQ,0BAAA;AAGP,SAAS,kBAAkB,IAAA,EAAuB;AACvD,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;AAMO,SAAS,gBAAgB,WAAA,EAA6B;AAC3D,EAAA,MAAM,QAAA,GAAW,YACd,IAAA,EAAK,CACL,MAAM,eAAe,CAAA,CACrB,OAAO,OAAO,CAAA;AACjB,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AAElC,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,GAAA,CAAI,CAAC,SAAS,KAAA,KAAU;AAC7C,IAAA,IAAI,KAAA,KAAU,CAAA,EAAG,OAAO,OAAA,CAAQ,WAAA,EAAY;AAC5C,IAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,KAAgB,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,EAAY;AAAA,EACxE,CAAC,CAAA;AAED,EAAA,IAAI,SAAS,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA,CAAE,OAAA,CAAQ,kBAAkB,EAAE,CAAA;AACxD,EAAA,IAAI,CAAC,QAAQ,OAAO,EAAA;AACpB,EAAA,IAAI,SAAS,IAAA,CAAK,MAAM,CAAA,EAAG,MAAA,GAAS,IAAI,MAAM,CAAA,CAAA;AAC9C,EAAA,IAAI,CAAC,KAAA,CAAM,IAAA,CAAK,MAAM,GAAG,OAAO,EAAA;AAChC,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,sBAAA,CAAuB,MAAc,QAAA,EAAqC;AACxF,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,OAAA,KAAY,MAAM,OAAO,IAAA;AAAA,EAC/B;AACA,EAAA,OAAO,KAAA;AACT;AAMO,SAAS,gBAAA,CAAiB,IAAA,EAAc,aAAA,GAAkC,EAAC,EAAW;AAC3F,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,aAAa,CAAA;AAClC,EAAA,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,IAAI,GAAG,OAAO,IAAA;AAC5B,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,EAAG,CAAC,EAAE,CAAA,EAAG,CAAA,EAAA;AAChC,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,CAAC,CAAA,CAAA;AACpB;AAGO,SAAS,qBAAA,CACd,WAAA,EACA,aAAA,GAAkC,EAAC,EAC3B;AACR,EAAA,MAAM,IAAA,GAAO,gBAAgB,WAAW,CAAA;AACxC,EAAA,IAAI,CAAC,MAAM,OAAO,EAAA;AAClB,EAAA,OAAO,gBAAA,CAAiB,MAAM,aAAa,CAAA;AAC7C;AAGO,SAAS,wBAAwB,KAAA,EAAyC;AAC/E,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA,EAAG,OAAO,IAAA;AAC3B,IAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA,EACf;AACA,EAAA,OAAO,IAAA;AACT","file":"inferIdentifier.js","sourcesContent":["const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** Whether `name` is a valid manifest table/field identifier. */\nexport function isValidIdentifier(name: string): boolean {\n return IDENT.test(name);\n}\n\n/**\n * Infer a camelCase identifier from human display text.\n * Does not pluralize/singularize — splits on non-alphanumeric runs only.\n */\nexport function inferIdentifier(displayName: string): string {\n const segments = displayName\n .trim()\n .split(/[^A-Za-z0-9]+/)\n .filter(Boolean);\n if (segments.length === 0) return \"\";\n\n const parts = segments.map((segment, index) => {\n if (index === 0) return segment.toLowerCase();\n return segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase();\n });\n\n let result = parts.join(\"\").replace(/[^A-Za-z0-9_]/g, \"\");\n if (!result) return \"\";\n if (/^[0-9]/.test(result)) result = `n${result}`;\n if (!IDENT.test(result)) return \"\";\n return result;\n}\n\n/** True when `name` appears in `siblings` (exact match). */\nexport function hasIdentifierCollision(name: string, siblings: Iterable<string>): boolean {\n for (const sibling of siblings) {\n if (sibling === name) return true;\n }\n return false;\n}\n\n/**\n * Return `base` or `base2`, `base3`, … when `base` is already taken.\n * Empty `base` is returned unchanged.\n */\nexport function uniqueIdentifier(base: string, reservedNames: Iterable<string> = []): string {\n if (!base) return base;\n const used = new Set(reservedNames);\n if (!used.has(base)) return base;\n let n = 2;\n while (used.has(`${base}${n}`)) n++;\n return `${base}${n}`;\n}\n\n/** Infer an identifier from display text, bumping with a numeric suffix when taken. */\nexport function inferUniqueIdentifier(\n displayName: string,\n reservedNames: Iterable<string> = [],\n): string {\n const base = inferIdentifier(displayName);\n if (!base) return \"\";\n return uniqueIdentifier(base, reservedNames);\n}\n\n/** First duplicate identifier in a list, if any. */\nexport function findDuplicateIdentifier(names: readonly string[]): string | null {\n const seen = new Set<string>();\n for (const name of names) {\n if (seen.has(name)) return name;\n seen.add(name);\n }\n return null;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type UndoRedoKind = "undo" | "redo" | null;
|
|
2
|
+
export declare function modKeyLabel(): string;
|
|
3
|
+
/** e.g. "⌘S" on Mac, "Ctrl+S" elsewhere */
|
|
4
|
+
export declare function formatModShortcut(key: string, shift?: boolean): string;
|
|
5
|
+
export declare function matchModShortcut(e: KeyboardEvent, key: string): boolean;
|
|
6
|
+
export declare function matchModShiftShortcut(e: KeyboardEvent, key: string): boolean;
|
|
7
|
+
export declare function matchModEnter(e: KeyboardEvent): boolean;
|
|
8
|
+
export declare function matchEscape(e: KeyboardEvent): boolean;
|
|
9
|
+
export declare function formatModEnterShortcut(): string;
|
|
10
|
+
export declare function formatEscapeShortcut(): string;
|
|
11
|
+
export declare function matchSave(e: KeyboardEvent): boolean;
|
|
12
|
+
export declare function matchToggleEditMode(e: KeyboardEvent): boolean;
|
|
13
|
+
export declare function matchUndoRedo(e: KeyboardEvent): UndoRedoKind;
|
|
14
|
+
export declare function isTypingInEditor(): boolean;
|
|
15
|
+
/** True when a Radix popover/select layer is open — Escape should close it first. */
|
|
16
|
+
export declare function isRadixOverlayOpen(): boolean;
|
package/dist/keyboard.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/keyboard.ts
|
|
2
|
+
function modKeyLabel() {
|
|
3
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? "\u2318" : "Ctrl";
|
|
4
|
+
}
|
|
5
|
+
function formatModShortcut(key, shift = false) {
|
|
6
|
+
const mod = modKeyLabel();
|
|
7
|
+
const k = shift ? `Shift+${key.toUpperCase()}` : key.toUpperCase();
|
|
8
|
+
return mod === "\u2318" ? `${mod}${shift ? "\u21E7" : ""}${key.toUpperCase()}` : `${mod}+${k}`;
|
|
9
|
+
}
|
|
10
|
+
function matchModShortcut(e, key) {
|
|
11
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey) return false;
|
|
12
|
+
return e.key.toLowerCase() === key.toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
function matchModShiftShortcut(e, key) {
|
|
15
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey || !e.shiftKey) return false;
|
|
16
|
+
return e.key.toLowerCase() === key.toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
function matchModEnter(e) {
|
|
19
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey || e.shiftKey) return false;
|
|
20
|
+
return e.key === "Enter";
|
|
21
|
+
}
|
|
22
|
+
function matchEscape(e) {
|
|
23
|
+
return e.key === "Escape" && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;
|
|
24
|
+
}
|
|
25
|
+
function formatModEnterShortcut() {
|
|
26
|
+
const mod = modKeyLabel();
|
|
27
|
+
return mod === "\u2318" ? "\u2318\u21B5" : "Ctrl+Enter";
|
|
28
|
+
}
|
|
29
|
+
function formatEscapeShortcut() {
|
|
30
|
+
return "Esc";
|
|
31
|
+
}
|
|
32
|
+
function matchSave(e) {
|
|
33
|
+
return matchModShortcut(e, "s");
|
|
34
|
+
}
|
|
35
|
+
function matchToggleEditMode(e) {
|
|
36
|
+
return matchModShortcut(e, "e");
|
|
37
|
+
}
|
|
38
|
+
function matchUndoRedo(e) {
|
|
39
|
+
if (!(e.metaKey || e.ctrlKey)) return null;
|
|
40
|
+
const key = e.key.toLowerCase();
|
|
41
|
+
if (key === "z" && !e.shiftKey) return "undo";
|
|
42
|
+
if (key === "z" && e.shiftKey || key === "y") return "redo";
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function isIdPickerPassThrough(el) {
|
|
46
|
+
if (el.tagName !== "INPUT") return false;
|
|
47
|
+
const input = el;
|
|
48
|
+
if (!input.hasAttribute("data-id-picker-input")) return false;
|
|
49
|
+
return input.getAttribute("aria-expanded") !== "true";
|
|
50
|
+
}
|
|
51
|
+
function isTypingInEditor() {
|
|
52
|
+
const ae = document.activeElement;
|
|
53
|
+
if (!ae) return false;
|
|
54
|
+
if (ae.closest(
|
|
55
|
+
".monaco-editor, .native-edit-context, [data-markdown-editor], [data-rich-text-editor]"
|
|
56
|
+
)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (isIdPickerPassThrough(ae)) return false;
|
|
60
|
+
const tag = ae.tagName;
|
|
61
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || ae.isContentEditable;
|
|
62
|
+
}
|
|
63
|
+
function isRadixOverlayOpen() {
|
|
64
|
+
const ae = document.activeElement;
|
|
65
|
+
if (ae?.getAttribute("aria-expanded") === "true") return true;
|
|
66
|
+
return Boolean(ae?.closest("[data-radix-popper-content-wrapper]"));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { formatEscapeShortcut, formatModEnterShortcut, formatModShortcut, isRadixOverlayOpen, isTypingInEditor, matchEscape, matchModEnter, matchModShiftShortcut, matchModShortcut, matchSave, matchToggleEditMode, matchUndoRedo, modKeyLabel };
|
|
70
|
+
//# sourceMappingURL=keyboard.js.map
|
|
71
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/keyboard.ts"],"names":[],"mappings":"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","file":"keyboard.js","sourcesContent":["/// <reference lib=\"dom\" />\n\nexport type UndoRedoKind = \"undo\" | \"redo\" | null;\n\nexport function modKeyLabel(): string {\n return /Mac|iPhone|iPad|iPod/.test(navigator.platform) ? \"⌘\" : \"Ctrl\";\n}\n\n/** e.g. \"⌘S\" on Mac, \"Ctrl+S\" elsewhere */\nexport function formatModShortcut(key: string, shift = false): string {\n const mod = modKeyLabel();\n const k = shift ? `Shift+${key.toUpperCase()}` : key.toUpperCase();\n return mod === \"⌘\" ? `${mod}${shift ? \"⇧\" : \"\"}${key.toUpperCase()}` : `${mod}+${k}`;\n}\n\nexport function matchModShortcut(e: KeyboardEvent, key: string): boolean {\n if (!(e.metaKey || e.ctrlKey) || e.altKey) return false;\n return e.key.toLowerCase() === key.toLowerCase();\n}\n\nexport function matchModShiftShortcut(e: KeyboardEvent, key: string): boolean {\n if (!(e.metaKey || e.ctrlKey) || e.altKey || !e.shiftKey) return false;\n return e.key.toLowerCase() === key.toLowerCase();\n}\n\nexport function matchModEnter(e: KeyboardEvent): boolean {\n if (!(e.metaKey || e.ctrlKey) || e.altKey || e.shiftKey) return false;\n return e.key === \"Enter\";\n}\n\nexport function matchEscape(e: KeyboardEvent): boolean {\n return e.key === \"Escape\" && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;\n}\n\nexport function formatModEnterShortcut(): string {\n const mod = modKeyLabel();\n return mod === \"⌘\" ? \"⌘↵\" : \"Ctrl+Enter\";\n}\n\nexport function formatEscapeShortcut(): string {\n return \"Esc\";\n}\n\nexport function matchSave(e: KeyboardEvent): boolean {\n return matchModShortcut(e, \"s\");\n}\n\nexport function matchToggleEditMode(e: KeyboardEvent): boolean {\n return matchModShortcut(e, \"e\");\n}\n\nexport function matchUndoRedo(e: KeyboardEvent): UndoRedoKind {\n if (!(e.metaKey || e.ctrlKey)) return null;\n const key = e.key.toLowerCase();\n if (key === \"z\" && !e.shiftKey) return \"undo\";\n if ((key === \"z\" && e.shiftKey) || key === \"y\") return \"redo\";\n return null;\n}\n\n// Id pickers use a combobox input; when the menu is closed, arrow keys should\n// move cell focus instead of being trapped in the field.\nfunction isIdPickerPassThrough(el: HTMLElement): boolean {\n if (el.tagName !== \"INPUT\") return false;\n const input = el as HTMLInputElement;\n if (!input.hasAttribute(\"data-id-picker-input\")) return false;\n return input.getAttribute(\"aria-expanded\") !== \"true\";\n}\n\n// Native browsers handle text undo for typed input; never hijack while a\n// caret-bearing element is focused.\nexport function isTypingInEditor(): boolean {\n const ae = document.activeElement as HTMLElement | null;\n if (!ae) return false;\n if (\n ae.closest(\n \".monaco-editor, .native-edit-context, [data-markdown-editor], [data-rich-text-editor]\",\n )\n ) {\n return true;\n }\n if (isIdPickerPassThrough(ae)) return false;\n const tag = ae.tagName;\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || ae.isContentEditable;\n}\n\n/** True when a Radix popover/select layer is open — Escape should close it first. */\nexport function isRadixOverlayOpen(): boolean {\n const ae = document.activeElement as HTMLElement | null;\n if (ae?.getAttribute(\"aria-expanded\") === \"true\") return true;\n return Boolean(ae?.closest(\"[data-radix-popper-content-wrapper]\"));\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const MANIFEST_DEPLOY_STATUSES: readonly ["idle", "cleaning", "ready", "deploying", "deployed", "failed"];
|
|
2
|
+
/** Status persisted in the singleton Columnus manifest row. */
|
|
3
|
+
export type ManifestDeployStatus = (typeof MANIFEST_DEPLOY_STATUSES)[number];
|
|
4
|
+
export declare function isManifestDeployStatus(value: unknown): value is ManifestDeployStatus;
|
|
5
|
+
/** Manifest row fields shared by the component, host, read API, CLI, and UI boundaries. */
|
|
6
|
+
export type ManifestDeploySnapshot = {
|
|
7
|
+
manifestJson: string;
|
|
8
|
+
draftManifestJson?: string;
|
|
9
|
+
draftUpdatedAt?: number;
|
|
10
|
+
erdLayoutJson?: string;
|
|
11
|
+
draftErdLayoutJson?: string;
|
|
12
|
+
lastSaveAt: number;
|
|
13
|
+
deployStatus: ManifestDeployStatus;
|
|
14
|
+
deployError?: string;
|
|
15
|
+
deployLogTail?: string;
|
|
16
|
+
deployedAt?: number;
|
|
17
|
+
deployOperationId?: string;
|
|
18
|
+
desiredManifestHash?: string;
|
|
19
|
+
deployedManifestHash?: string;
|
|
20
|
+
deployStartedAt?: number;
|
|
21
|
+
deployCompletedAt?: number;
|
|
22
|
+
refsSchemaHash?: string;
|
|
23
|
+
refsIndexedAt?: number;
|
|
24
|
+
refsRecomputeTargetHash?: string;
|
|
25
|
+
refsRecomputeHeartbeatAt?: number;
|
|
26
|
+
pendingRefsRecomputeBeforeJson?: string;
|
|
27
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/manifestDeploy.ts
|
|
2
|
+
var MANIFEST_DEPLOY_STATUSES = [
|
|
3
|
+
"idle",
|
|
4
|
+
"cleaning",
|
|
5
|
+
"ready",
|
|
6
|
+
"deploying",
|
|
7
|
+
"deployed",
|
|
8
|
+
"failed"
|
|
9
|
+
];
|
|
10
|
+
function isManifestDeployStatus(value) {
|
|
11
|
+
return MANIFEST_DEPLOY_STATUSES.some((status) => status === value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { MANIFEST_DEPLOY_STATUSES, isManifestDeployStatus };
|
|
15
|
+
//# sourceMappingURL=manifestDeploy.js.map
|
|
16
|
+
//# sourceMappingURL=manifestDeploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/manifestDeploy.ts"],"names":[],"mappings":";AAAO,IAAM,wBAAA,GAA2B;AAAA,EACtC,MAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF;AAKO,SAAS,uBAAuB,KAAA,EAA+C;AACpF,EAAA,OAAO,wBAAA,CAAyB,IAAA,CAAK,CAAC,MAAA,KAAW,WAAW,KAAK,CAAA;AACnE","file":"manifestDeploy.js","sourcesContent":["export const MANIFEST_DEPLOY_STATUSES = [\n \"idle\",\n \"cleaning\",\n \"ready\",\n \"deploying\",\n \"deployed\",\n \"failed\",\n] as const;\n\n/** Status persisted in the singleton Columnus manifest row. */\nexport type ManifestDeployStatus = (typeof MANIFEST_DEPLOY_STATUSES)[number];\n\nexport function isManifestDeployStatus(value: unknown): value is ManifestDeployStatus {\n return MANIFEST_DEPLOY_STATUSES.some((status) => status === value);\n}\n\n/** Manifest row fields shared by the component, host, read API, CLI, and UI boundaries. */\nexport type ManifestDeploySnapshot = {\n manifestJson: string;\n draftManifestJson?: string;\n draftUpdatedAt?: number;\n erdLayoutJson?: string;\n draftErdLayoutJson?: string;\n lastSaveAt: number;\n deployStatus: ManifestDeployStatus;\n deployError?: string;\n deployLogTail?: string;\n deployedAt?: number;\n deployOperationId?: string;\n desiredManifestHash?: string;\n deployedManifestHash?: string;\n deployStartedAt?: number;\n deployCompletedAt?: number;\n refsSchemaHash?: string;\n refsIndexedAt?: number;\n refsRecomputeTargetHash?: string;\n refsRecomputeHeartbeatAt?: number;\n pendingRefsRecomputeBeforeJson?: string;\n};\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Manifest } from "./schemaTypes.ts";
|
|
2
|
+
import type { ApiTableAccess } from "./apiProfile.ts";
|
|
3
|
+
/** Stable JSON for `{ tables }` filtered to an allowlist (sorted by table name). */
|
|
4
|
+
export declare function canonicalManifestTablesJson(manifest: Manifest, allowedTableNames?: readonly string[]): string;
|
|
5
|
+
/** SHA-256 hex of canonical filtered manifest tables (server source of truth for pull sync). */
|
|
6
|
+
export declare function computeManifestHash(manifest: Manifest, allowedTableNames?: readonly string[]): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Pull-sync fingerprint for a manifest plus the profile capabilities that
|
|
9
|
+
* determine generated client refs and hooks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeManifestAccessHash(manifest: Manifest, tableAccess: readonly ApiTableAccess[]): Promise<string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/manifestHash.ts
|
|
2
|
+
function canonicalManifestTablesJson(manifest, allowedTableNames) {
|
|
3
|
+
let tables = manifest.tables;
|
|
4
|
+
if (allowedTableNames !== void 0) {
|
|
5
|
+
const allowed = new Set(allowedTableNames);
|
|
6
|
+
tables = tables.filter((table) => allowed.has(table.name));
|
|
7
|
+
}
|
|
8
|
+
const sorted = [...tables].sort((a, b) => a.name.localeCompare(b.name));
|
|
9
|
+
return JSON.stringify({ tables: sorted });
|
|
10
|
+
}
|
|
11
|
+
async function sha256Hex(input) {
|
|
12
|
+
const data = new TextEncoder().encode(input);
|
|
13
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
14
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
15
|
+
}
|
|
16
|
+
async function computeManifestHash(manifest, allowedTableNames) {
|
|
17
|
+
return sha256Hex(canonicalManifestTablesJson(manifest, allowedTableNames));
|
|
18
|
+
}
|
|
19
|
+
async function computeManifestAccessHash(manifest, tableAccess) {
|
|
20
|
+
return sha256Hex(
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
manifest: JSON.parse(
|
|
23
|
+
canonicalManifestTablesJson(
|
|
24
|
+
manifest,
|
|
25
|
+
tableAccess.map((access) => access.table)
|
|
26
|
+
)
|
|
27
|
+
),
|
|
28
|
+
tableAccess
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { canonicalManifestTablesJson, computeManifestAccessHash, computeManifestHash };
|
|
34
|
+
//# sourceMappingURL=manifestHash.js.map
|
|
35
|
+
//# sourceMappingURL=manifestHash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/manifestHash.ts"],"names":[],"mappings":";AAIO,SAAS,2BAAA,CACd,UACA,iBAAA,EACQ;AACR,EAAA,IAAI,SAAkB,QAAA,CAAS,MAAA;AAC/B,EAAA,IAAI,sBAAsB,MAAA,EAAW;AACnC,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,iBAAiB,CAAA;AACzC,IAAA,MAAA,GAAS,MAAA,CAAO,OAAO,CAAC,KAAA,KAAU,QAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAC,CAAA;AAAA,EAC3D;AACA,EAAA,MAAM,MAAA,GAAS,CAAC,GAAG,MAAM,EAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,IAAA,CAAK,aAAA,CAAc,CAAA,CAAE,IAAI,CAAC,CAAA;AACtE,EAAA,OAAO,IAAA,CAAK,SAAA,CAAU,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAC1C;AAEA,eAAe,UAAU,KAAA,EAAgC;AACvD,EAAA,MAAM,IAAA,GAAO,IAAI,WAAA,EAAY,CAAE,OAAO,KAAK,CAAA;AAC3C,EAAA,MAAM,SAAS,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,WAAW,IAAI,CAAA;AACzD,EAAA,OAAO,CAAC,GAAG,IAAI,UAAA,CAAW,MAAM,CAAC,CAAA,CAAE,IAAI,CAAC,IAAA,KAAS,KAAK,QAAA,CAAS,EAAE,EAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC9F;AAGA,eAAsB,mBAAA,CACpB,UACA,iBAAA,EACiB;AACjB,EAAA,OAAO,SAAA,CAAU,2BAAA,CAA4B,QAAA,EAAU,iBAAiB,CAAC,CAAA;AAC3E;AAMA,eAAsB,yBAAA,CACpB,UACA,WAAA,EACiB;AACjB,EAAA,OAAO,SAAA;AAAA,IACL,KAAK,SAAA,CAAU;AAAA,MACb,UAAU,IAAA,CAAK,KAAA;AAAA,QACb,2BAAA;AAAA,UACE,QAAA;AAAA,UACA,WAAA,CAAY,GAAA,CAAI,CAAC,MAAA,KAAW,OAAO,KAAK;AAAA;AAC1C,OACF;AAAA,MACA;AAAA,KACD;AAAA,GACH;AACF","file":"manifestHash.js","sourcesContent":["import type { Manifest, Table } from \"./schemaTypes.ts\";\nimport type { ApiTableAccess } from \"./apiProfile.ts\";\n\n/** Stable JSON for `{ tables }` filtered to an allowlist (sorted by table name). */\nexport function canonicalManifestTablesJson(\n manifest: Manifest,\n allowedTableNames?: readonly string[],\n): string {\n let tables: Table[] = manifest.tables;\n if (allowedTableNames !== undefined) {\n const allowed = new Set(allowedTableNames);\n tables = tables.filter((table) => allowed.has(table.name));\n }\n const sorted = [...tables].sort((a, b) => a.name.localeCompare(b.name));\n return JSON.stringify({ tables: sorted });\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const digest = await crypto.subtle.digest(\"SHA-256\", data);\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** SHA-256 hex of canonical filtered manifest tables (server source of truth for pull sync). */\nexport async function computeManifestHash(\n manifest: Manifest,\n allowedTableNames?: readonly string[],\n): Promise<string> {\n return sha256Hex(canonicalManifestTablesJson(manifest, allowedTableNames));\n}\n\n/**\n * Pull-sync fingerprint for a manifest plus the profile capabilities that\n * determine generated client refs and hooks.\n */\nexport async function computeManifestAccessHash(\n manifest: Manifest,\n tableAccess: readonly ApiTableAccess[],\n): Promise<string> {\n return sha256Hex(\n JSON.stringify({\n manifest: JSON.parse(\n canonicalManifestTablesJson(\n manifest,\n tableAccess.map((access) => access.table),\n ),\n ),\n tableAccess,\n }),\n );\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitized Convex preview deployment name for CI / Coolify preview builds.
|
|
3
|
+
* "/" breaks preview/<name> lookups in Convex deployment references.
|
|
4
|
+
*
|
|
5
|
+
* Coolify preview env vars: COOLIFY_BRANCH, COOLIFY_IS_PREVIEW, COOLIFY_FQDN,
|
|
6
|
+
* COOLIFY_URL (BuildKit build secrets).
|
|
7
|
+
*/
|
|
8
|
+
export declare function coolifyHostnameFromEnv(env?: NodeJS.ProcessEnv): string | null;
|
|
9
|
+
/** True when Coolify (or CI_PREVIEW) indicates a PR preview Docker build. */
|
|
10
|
+
export declare function isCiPreviewBuild(env?: NodeJS.ProcessEnv): boolean;
|
|
11
|
+
/** PR id from `preview-<n>.<domain>` / `manage-preview-<n>` hostname or `pull/<n>/head` branch. */
|
|
12
|
+
export declare function resolvePreviewPrId(env?: NodeJS.ProcessEnv): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the git branch used for Convex `--preview-name`.
|
|
15
|
+
* Coolify often sets COOLIFY_BRANCH to `pull/<n>/head` at runtime (see coollabsio/coolify#9572).
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePreviewBranch(env?: NodeJS.ProcessEnv): string | null;
|
|
18
|
+
export declare function sanitizePreviewDeployName(branch: string): string;
|
|
19
|
+
/** Normalize a preview name from Convex env before `convex deploy --preview-name`. */
|
|
20
|
+
export declare function normalizePreviewDeployName(name: string): string;
|
|
21
|
+
export declare function ciPreviewDeployName(env?: NodeJS.ProcessEnv): string | null;
|
|
22
|
+
/** Stale failures from broken preview deploy before the bridge was live. */
|
|
23
|
+
export declare const STALE_ENDPOINT_DEPLOY_ERROR: RegExp;
|
|
24
|
+
export declare function isStaleEndpointDeployError(error: string | undefined): boolean;
|
|
25
|
+
/** Infra failures from a prior broken deploy that a fresh save can retry. */
|
|
26
|
+
export declare function isRecoverableDeployError(error: string | undefined): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/previewDeployName.ts
|
|
2
|
+
function coolifyHostnameFromEnv(env = process.env) {
|
|
3
|
+
const raw = env.COOLIFY_FQDN ?? env.COOLIFY_URL;
|
|
4
|
+
if (!raw) return null;
|
|
5
|
+
const host = raw.replace(/^https?:\/\//, "").split("/")[0] ?? "";
|
|
6
|
+
return host || null;
|
|
7
|
+
}
|
|
8
|
+
function isCiPreviewBuild(env = process.env) {
|
|
9
|
+
if (env.CI_PREVIEW === "true" || env.COOLIFY_IS_PREVIEW === "true") {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
const host = coolifyHostnameFromEnv(env);
|
|
13
|
+
return Boolean(host && /^(?:manage-|deploy-)?preview-\d+\./.test(host));
|
|
14
|
+
}
|
|
15
|
+
var PULL_HEAD_BRANCH_RE = /^pull\/(\d+)\/head$/i;
|
|
16
|
+
var PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-)?preview-(\d+)\./;
|
|
17
|
+
function resolvePreviewPrId(env = process.env) {
|
|
18
|
+
const host = coolifyHostnameFromEnv(env);
|
|
19
|
+
if (host) {
|
|
20
|
+
const fromHost = PREVIEW_HOST_PR_RE.exec(host)?.[1];
|
|
21
|
+
if (fromHost) return fromHost;
|
|
22
|
+
}
|
|
23
|
+
const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;
|
|
24
|
+
if (branch) {
|
|
25
|
+
const fromBranch = PULL_HEAD_BRANCH_RE.exec(stripQualifiedBranch(branch))?.[1];
|
|
26
|
+
if (fromBranch) return fromBranch;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function resolvePreviewBranch(env = process.env) {
|
|
31
|
+
if (env.GITHUB_HEAD_REF) return stripQualifiedBranch(env.GITHUB_HEAD_REF);
|
|
32
|
+
const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;
|
|
33
|
+
const prId = resolvePreviewPrId(env);
|
|
34
|
+
if (!branch) {
|
|
35
|
+
return prId ? `preview-${prId}` : null;
|
|
36
|
+
}
|
|
37
|
+
const stripped = stripQualifiedBranch(branch);
|
|
38
|
+
if (PULL_HEAD_BRANCH_RE.test(stripped)) {
|
|
39
|
+
if (prId) return `preview-${prId}`;
|
|
40
|
+
}
|
|
41
|
+
if ((stripped === "main" || stripped === "master") && prId) {
|
|
42
|
+
return `preview-${prId}`;
|
|
43
|
+
}
|
|
44
|
+
return stripped;
|
|
45
|
+
}
|
|
46
|
+
function stripQualifiedBranch(branch) {
|
|
47
|
+
const colon = branch.indexOf(":");
|
|
48
|
+
return colon > 0 ? branch.slice(0, colon) : branch;
|
|
49
|
+
}
|
|
50
|
+
function sanitizePreviewDeployName(branch) {
|
|
51
|
+
return stripQualifiedBranch(branch).replace(/\//g, "-");
|
|
52
|
+
}
|
|
53
|
+
function normalizePreviewDeployName(name) {
|
|
54
|
+
return sanitizePreviewDeployName(name.trim());
|
|
55
|
+
}
|
|
56
|
+
function ciPreviewDeployName(env = process.env) {
|
|
57
|
+
if (!isCiPreviewBuild(env)) return null;
|
|
58
|
+
const branch = resolvePreviewBranch(env);
|
|
59
|
+
if (!branch) return null;
|
|
60
|
+
return sanitizePreviewDeployName(branch);
|
|
61
|
+
}
|
|
62
|
+
var STALE_ENDPOINT_DEPLOY_ERROR = /^deploy endpoint returned (?:200(?: OK)?|500(?: Internal Server Error)?)$/;
|
|
63
|
+
function isStaleEndpointDeployError(error) {
|
|
64
|
+
return error != null && STALE_ENDPOINT_DEPLOY_ERROR.test(error);
|
|
65
|
+
}
|
|
66
|
+
function isRecoverableDeployError(error) {
|
|
67
|
+
if (error == null) return false;
|
|
68
|
+
if (isStaleEndpointDeployError(error)) return true;
|
|
69
|
+
if (error.includes("502 Bad Gateway")) return true;
|
|
70
|
+
if (error.includes("deploy endpoint unreachable")) return true;
|
|
71
|
+
if (error.includes("deploy endpoint timed out")) return true;
|
|
72
|
+
return error.includes("convex/bin/main.js") || error.includes("convex CLI (bin/main.js") || error.includes("convex CLI not available");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { STALE_ENDPOINT_DEPLOY_ERROR, ciPreviewDeployName, coolifyHostnameFromEnv, isCiPreviewBuild, isRecoverableDeployError, isStaleEndpointDeployError, normalizePreviewDeployName, resolvePreviewBranch, resolvePreviewPrId, sanitizePreviewDeployName };
|
|
76
|
+
//# sourceMappingURL=previewDeployName.js.map
|
|
77
|
+
//# sourceMappingURL=previewDeployName.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/previewDeployName.ts"],"names":[],"mappings":"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","file":"previewDeployName.js","sourcesContent":["/**\n * Sanitized Convex preview deployment name for CI / Coolify preview builds.\n * \"/\" breaks preview/<name> lookups in Convex deployment references.\n *\n * Coolify preview env vars: COOLIFY_BRANCH, COOLIFY_IS_PREVIEW, COOLIFY_FQDN,\n * COOLIFY_URL (BuildKit build secrets).\n */\nexport function coolifyHostnameFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {\n const raw = env.COOLIFY_FQDN ?? env.COOLIFY_URL;\n if (!raw) return null;\n const host = raw.replace(/^https?:\\/\\//, \"\").split(\"/\")[0] ?? \"\";\n return host || null;\n}\n\n/** True when Coolify (or CI_PREVIEW) indicates a PR preview Docker build. */\nexport function isCiPreviewBuild(env: NodeJS.ProcessEnv = process.env): boolean {\n if (env.CI_PREVIEW === \"true\" || env.COOLIFY_IS_PREVIEW === \"true\") {\n return true;\n }\n const host = coolifyHostnameFromEnv(env);\n // preview-<pr>, manage-preview-<pr>, deploy-preview-<pr>\n return Boolean(host && /^(?:manage-|deploy-)?preview-\\d+\\./.test(host));\n}\n\nconst PULL_HEAD_BRANCH_RE = /^pull\\/(\\d+)\\/head$/i;\nconst PREVIEW_HOST_PR_RE = /^(?:manage-|deploy-)?preview-(\\d+)\\./;\n\n/** PR id from `preview-<n>.<domain>` / `manage-preview-<n>` hostname or `pull/<n>/head` branch. */\nexport function resolvePreviewPrId(env: NodeJS.ProcessEnv = process.env): string | null {\n const host = coolifyHostnameFromEnv(env);\n if (host) {\n const fromHost = PREVIEW_HOST_PR_RE.exec(host)?.[1];\n if (fromHost) return fromHost;\n }\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n if (branch) {\n const fromBranch = PULL_HEAD_BRANCH_RE.exec(stripQualifiedBranch(branch))?.[1];\n if (fromBranch) return fromBranch;\n }\n\n return null;\n}\n\n/**\n * Resolve the git branch used for Convex `--preview-name`.\n * Coolify often sets COOLIFY_BRANCH to `pull/<n>/head` at runtime (see coollabsio/coolify#9572).\n */\nexport function resolvePreviewBranch(env: NodeJS.ProcessEnv = process.env): string | null {\n if (env.GITHUB_HEAD_REF) return stripQualifiedBranch(env.GITHUB_HEAD_REF);\n\n const branch = env.COOLIFY_BRANCH ?? env.GIT_BRANCH;\n const prId = resolvePreviewPrId(env);\n if (!branch) {\n return prId ? `preview-${prId}` : null;\n }\n\n const stripped = stripQualifiedBranch(branch);\n if (PULL_HEAD_BRANCH_RE.test(stripped)) {\n if (prId) return `preview-${prId}`;\n }\n // Coolify PR apps often leave COOLIFY_BRANCH=main while FQDN is *-preview-<n>.\n if ((stripped === \"main\" || stripped === \"master\") && prId) {\n return `preview-${prId}`;\n }\n return stripped;\n}\n\n/** Strip Coolify/Convex qualified suffixes (e.g. `pull-48-head:pr-48-coolify`). */\nfunction stripQualifiedBranch(branch: string): string {\n const colon = branch.indexOf(\":\");\n return colon > 0 ? branch.slice(0, colon) : branch;\n}\n\nexport function sanitizePreviewDeployName(branch: string): string {\n return stripQualifiedBranch(branch).replace(/\\//g, \"-\");\n}\n\n/** Normalize a preview name from Convex env before `convex deploy --preview-name`. */\nexport function normalizePreviewDeployName(name: string): string {\n return sanitizePreviewDeployName(name.trim());\n}\n\nexport function ciPreviewDeployName(env: NodeJS.ProcessEnv = process.env): string | null {\n if (!isCiPreviewBuild(env)) return null;\n\n const branch = resolvePreviewBranch(env);\n if (!branch) return null;\n return sanitizePreviewDeployName(branch);\n}\n\n/** Stale failures from broken preview deploy before the bridge was live. */\nexport const STALE_ENDPOINT_DEPLOY_ERROR =\n /^deploy endpoint returned (?:200(?: OK)?|500(?: Internal Server Error)?)$/;\n\nexport function isStaleEndpointDeployError(error: string | undefined): boolean {\n return error != null && STALE_ENDPOINT_DEPLOY_ERROR.test(error);\n}\n\n/** Infra failures from a prior broken deploy that a fresh save can retry. */\nexport function isRecoverableDeployError(error: string | undefined): boolean {\n if (error == null) return false;\n if (isStaleEndpointDeployError(error)) return true;\n if (error.includes(\"502 Bad Gateway\")) return true;\n if (error.includes(\"deploy endpoint unreachable\")) return true;\n if (error.includes(\"deploy endpoint timed out\")) return true;\n return (\n error.includes(\"convex/bin/main.js\") ||\n error.includes(\"convex CLI (bin/main.js\") ||\n error.includes(\"convex CLI not available\")\n );\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ProposalOperation = "insert" | "update" | "delete";
|
|
2
|
+
export type ProposalAccess = {
|
|
3
|
+
table: string;
|
|
4
|
+
ops: Record<ProposalOperation, boolean>;
|
|
5
|
+
recordIds?: string[];
|
|
6
|
+
allowedFields?: string[];
|
|
7
|
+
};
|
|
8
|
+
export type ProposalTarget = {
|
|
9
|
+
table: string;
|
|
10
|
+
operation: ProposalOperation;
|
|
11
|
+
rowId?: string;
|
|
12
|
+
fields?: string[];
|
|
13
|
+
};
|
|
14
|
+
/** Enforce the proposal rules shared by API profiles and share scopes. */
|
|
15
|
+
export declare function assertProposalAccess(accesses: readonly ProposalAccess[], target: ProposalTarget): ProposalAccess;
|
|
16
|
+
/** Strip system and disallowed fields before persisting a proposal patch. */
|
|
17
|
+
export declare function pickProposalFields(fields: Record<string, unknown>, allowedFields?: string[]): Record<string, unknown>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/proposePolicy.ts
|
|
2
|
+
function assertProposalAccess(accesses, target) {
|
|
3
|
+
const access = accesses.find((candidate) => candidate.table === target.table);
|
|
4
|
+
if (!access?.ops[target.operation]) {
|
|
5
|
+
throw new Error(`${target.operation} is not allowed for ${target.table}`);
|
|
6
|
+
}
|
|
7
|
+
if (access.recordIds && (!target.rowId || !access.recordIds.includes(target.rowId))) {
|
|
8
|
+
throw new Error(`Row is outside the allowed scope for ${target.table}`);
|
|
9
|
+
}
|
|
10
|
+
if (target.operation === "insert" && access.recordIds) {
|
|
11
|
+
throw new Error(`Insert is not allowed on a record-restricted ${target.table} scope`);
|
|
12
|
+
}
|
|
13
|
+
if (target.fields) {
|
|
14
|
+
const allowed = access.allowedFields ? new Set(access.allowedFields) : null;
|
|
15
|
+
const denied = target.fields.find(
|
|
16
|
+
(field) => field.startsWith("_") || allowed && !allowed.has(field)
|
|
17
|
+
);
|
|
18
|
+
if (denied) throw new Error(`Field ${denied} is not allowed for ${target.table}`);
|
|
19
|
+
}
|
|
20
|
+
return access;
|
|
21
|
+
}
|
|
22
|
+
function pickProposalFields(fields, allowedFields) {
|
|
23
|
+
const allowed = allowedFields ? new Set(allowedFields) : null;
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
Object.entries(fields).filter(
|
|
26
|
+
([field]) => !field.startsWith("_") && (!allowed || allowed.has(field))
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { assertProposalAccess, pickProposalFields };
|
|
32
|
+
//# sourceMappingURL=proposePolicy.js.map
|
|
33
|
+
//# sourceMappingURL=proposePolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/proposePolicy.ts"],"names":[],"mappings":";AAiBO,SAAS,oBAAA,CACd,UACA,MAAA,EACgB;AAChB,EAAA,MAAM,MAAA,GAAS,SAAS,IAAA,CAAK,CAAC,cAAc,SAAA,CAAU,KAAA,KAAU,OAAO,KAAK,CAAA;AAC5E,EAAA,IAAI,CAAC,MAAA,EAAQ,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AAClC,IAAA,MAAM,IAAI,MAAM,CAAA,EAAG,MAAA,CAAO,SAAS,CAAA,oBAAA,EAAuB,MAAA,CAAO,KAAK,CAAA,CAAE,CAAA;AAAA,EAC1E;AACA,EAAA,IAAI,MAAA,CAAO,SAAA,KAAc,CAAC,MAAA,CAAO,KAAA,IAAS,CAAC,MAAA,CAAO,SAAA,CAAU,QAAA,CAAS,MAAA,CAAO,KAAK,CAAA,CAAA,EAAI;AACnF,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,MAAA,CAAO,KAAK,CAAA,CAAE,CAAA;AAAA,EACxE;AACA,EAAA,IAAI,MAAA,CAAO,SAAA,KAAc,QAAA,IAAY,MAAA,CAAO,SAAA,EAAW;AACrD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6CAAA,EAAgD,MAAA,CAAO,KAAK,CAAA,MAAA,CAAQ,CAAA;AAAA,EACtF;AACA,EAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,IAAA,MAAM,UAAU,MAAA,CAAO,aAAA,GAAgB,IAAI,GAAA,CAAI,MAAA,CAAO,aAAa,CAAA,GAAI,IAAA;AACvE,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,CAAO,IAAA;AAAA,MAC3B,CAAC,KAAA,KAAU,KAAA,CAAM,UAAA,CAAW,GAAG,KAAM,OAAA,IAAW,CAAC,OAAA,CAAQ,GAAA,CAAI,KAAK;AAAA,KACpE;AACA,IAAA,IAAI,MAAA,QAAc,IAAI,KAAA,CAAM,SAAS,MAAM,CAAA,oBAAA,EAAuB,MAAA,CAAO,KAAK,CAAA,CAAE,CAAA;AAAA,EAClF;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,kBAAA,CACd,QACA,aAAA,EACyB;AACzB,EAAA,MAAM,OAAA,GAAU,aAAA,GAAgB,IAAI,GAAA,CAAI,aAAa,CAAA,GAAI,IAAA;AACzD,EAAA,OAAO,MAAA,CAAO,WAAA;AAAA,IACZ,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAA;AAAA,MACrB,CAAC,CAAC,KAAK,CAAA,KAAM,CAAC,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,KAAM,CAAC,OAAA,IAAW,OAAA,CAAQ,IAAI,KAAK,CAAA;AAAA;AACvE,GACF;AACF","file":"proposePolicy.js","sourcesContent":["export type ProposalOperation = \"insert\" | \"update\" | \"delete\";\n\nexport type ProposalAccess = {\n table: string;\n ops: Record<ProposalOperation, boolean>;\n recordIds?: string[];\n allowedFields?: string[];\n};\n\nexport type ProposalTarget = {\n table: string;\n operation: ProposalOperation;\n rowId?: string;\n fields?: string[];\n};\n\n/** Enforce the proposal rules shared by API profiles and share scopes. */\nexport function assertProposalAccess(\n accesses: readonly ProposalAccess[],\n target: ProposalTarget,\n): ProposalAccess {\n const access = accesses.find((candidate) => candidate.table === target.table);\n if (!access?.ops[target.operation]) {\n throw new Error(`${target.operation} is not allowed for ${target.table}`);\n }\n if (access.recordIds && (!target.rowId || !access.recordIds.includes(target.rowId))) {\n throw new Error(`Row is outside the allowed scope for ${target.table}`);\n }\n if (target.operation === \"insert\" && access.recordIds) {\n throw new Error(`Insert is not allowed on a record-restricted ${target.table} scope`);\n }\n if (target.fields) {\n const allowed = access.allowedFields ? new Set(access.allowedFields) : null;\n const denied = target.fields.find(\n (field) => field.startsWith(\"_\") || (allowed && !allowed.has(field)),\n );\n if (denied) throw new Error(`Field ${denied} is not allowed for ${target.table}`);\n }\n return access;\n}\n\n/** Strip system and disallowed fields before persisting a proposal patch. */\nexport function pickProposalFields(\n fields: Record<string, unknown>,\n allowedFields?: string[],\n): Record<string, unknown> {\n const allowed = allowedFields ? new Set(allowedFields) : null;\n return Object.fromEntries(\n Object.entries(fields).filter(\n ([field]) => !field.startsWith(\"_\") && (!allowed || allowed.has(field)),\n ),\n );\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type BetweenValue = {
|
|
2
|
+
min: unknown;
|
|
3
|
+
max: unknown;
|
|
4
|
+
};
|
|
5
|
+
export type BetweenIndexBound = {
|
|
6
|
+
op: "gte";
|
|
7
|
+
value: unknown;
|
|
8
|
+
} | {
|
|
9
|
+
op: "lte";
|
|
10
|
+
value: unknown;
|
|
11
|
+
} | {
|
|
12
|
+
op: "between";
|
|
13
|
+
min: unknown;
|
|
14
|
+
max: unknown;
|
|
15
|
+
};
|
|
16
|
+
export declare function isCompleteBetween(value: BetweenValue): boolean;
|
|
17
|
+
/** Convert inclusive `between` UI state into the strongest available index range. */
|
|
18
|
+
export declare function betweenToIndexBound(value: BetweenValue): BetweenIndexBound | null;
|
|
19
|
+
export declare function matchesBetween(value: unknown, between: BetweenValue): boolean;
|
|
20
|
+
export declare function compareOrdered(a: unknown, b: unknown): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/queryBetween.ts
|
|
2
|
+
function isCompleteBetween(value) {
|
|
3
|
+
return value.min != null && value.max != null;
|
|
4
|
+
}
|
|
5
|
+
function betweenToIndexBound(value) {
|
|
6
|
+
if (isCompleteBetween(value)) {
|
|
7
|
+
return { op: "between", min: value.min, max: value.max };
|
|
8
|
+
}
|
|
9
|
+
if (value.min != null) return { op: "gte", value: value.min };
|
|
10
|
+
if (value.max != null) return { op: "lte", value: value.max };
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function matchesBetween(value, between) {
|
|
14
|
+
if (between.min != null && compareOrdered(value, between.min) < 0) return false;
|
|
15
|
+
if (between.max != null && compareOrdered(value, between.max) > 0) return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function compareOrdered(a, b) {
|
|
19
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
20
|
+
if (typeof a === "string" && typeof b === "string") return a < b ? -1 : a > b ? 1 : 0;
|
|
21
|
+
if (typeof a === "bigint" && typeof b === "bigint") return a < b ? -1 : a > b ? 1 : 0;
|
|
22
|
+
if (a === b) return 0;
|
|
23
|
+
return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { betweenToIndexBound, compareOrdered, isCompleteBetween, matchesBetween };
|
|
27
|
+
//# sourceMappingURL=queryBetween.js.map
|
|
28
|
+
//# sourceMappingURL=queryBetween.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queryBetween.ts"],"names":[],"mappings":";AAUO,SAAS,kBAAkB,KAAA,EAA8B;AAC9D,EAAA,OAAO,KAAA,CAAM,GAAA,IAAO,IAAA,IAAQ,KAAA,CAAM,GAAA,IAAO,IAAA;AAC3C;AAGO,SAAS,oBAAoB,KAAA,EAA+C;AACjF,EAAA,IAAI,iBAAA,CAAkB,KAAK,CAAA,EAAG;AAC5B,IAAA,OAAO,EAAE,IAAI,SAAA,EAAW,GAAA,EAAK,MAAM,GAAA,EAAK,GAAA,EAAK,MAAM,GAAA,EAAI;AAAA,EACzD;AACA,EAAA,IAAI,KAAA,CAAM,OAAO,IAAA,EAAM,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,GAAA,EAAI;AAC5D,EAAA,IAAI,KAAA,CAAM,OAAO,IAAA,EAAM,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,GAAA,EAAI;AAC5D,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,cAAA,CAAe,OAAgB,OAAA,EAAgC;AAC7E,EAAA,IAAI,OAAA,CAAQ,OAAO,IAAA,IAAQ,cAAA,CAAe,OAAO,OAAA,CAAQ,GAAG,CAAA,GAAI,CAAA,EAAG,OAAO,KAAA;AAC1E,EAAA,IAAI,OAAA,CAAQ,OAAO,IAAA,IAAQ,cAAA,CAAe,OAAO,OAAA,CAAQ,GAAG,CAAA,GAAI,CAAA,EAAG,OAAO,KAAA;AAC1E,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,cAAA,CAAe,GAAY,CAAA,EAAoB;AAC7D,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,QAAA,SAAiB,CAAA,GAAI,CAAA;AAC/D,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,CAAA,GAAI,CAAA,GAAI,EAAA,GAAK,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA;AACpF,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,CAAA,GAAI,CAAA,GAAI,EAAA,GAAK,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA;AACpF,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,CAAA;AACpB,EAAA,OAAO,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,GAAI,EAAA,GAAK,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,CAAO,CAAC,IAAI,CAAA,GAAI,CAAA;AAClE","file":"queryBetween.js","sourcesContent":["export type BetweenValue = {\n min: unknown;\n max: unknown;\n};\n\nexport type BetweenIndexBound =\n | { op: \"gte\"; value: unknown }\n | { op: \"lte\"; value: unknown }\n | { op: \"between\"; min: unknown; max: unknown };\n\nexport function isCompleteBetween(value: BetweenValue): boolean {\n return value.min != null && value.max != null;\n}\n\n/** Convert inclusive `between` UI state into the strongest available index range. */\nexport function betweenToIndexBound(value: BetweenValue): BetweenIndexBound | null {\n if (isCompleteBetween(value)) {\n return { op: \"between\", min: value.min, max: value.max };\n }\n if (value.min != null) return { op: \"gte\", value: value.min };\n if (value.max != null) return { op: \"lte\", value: value.max };\n return null;\n}\n\nexport function matchesBetween(value: unknown, between: BetweenValue): boolean {\n if (between.min != null && compareOrdered(value, between.min) < 0) return false;\n if (between.max != null && compareOrdered(value, between.max) > 0) return false;\n return true;\n}\n\nexport function compareOrdered(a: unknown, b: unknown): number {\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"string\" && typeof b === \"string\") return a < b ? -1 : a > b ? 1 : 0;\n if (typeof a === \"bigint\" && typeof b === \"bigint\") return a < b ? -1 : a > b ? 1 : 0;\n if (a === b) return 0;\n return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Predicate, QueryOrder } from "./queryTypes.ts";
|
|
2
|
+
/** Evaluate residual predicates against a document. */
|
|
3
|
+
export declare function documentMatchesPredicates(doc: Record<string, unknown>, predicates: Predicate[]): boolean;
|
|
4
|
+
export declare function documentMatchesPredicate(doc: Record<string, unknown>, predicate: Predicate): boolean;
|
|
5
|
+
export declare function sortDocuments<T extends Record<string, unknown>>(docs: T[], order: QueryOrder): T[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// packages/@columnus/shared/src/queryBetween.ts
|
|
2
|
+
function matchesBetween(value, between) {
|
|
3
|
+
if (between.min != null && compareOrdered(value, between.min) < 0) return false;
|
|
4
|
+
if (between.max != null && compareOrdered(value, between.max) > 0) return false;
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
function compareOrdered(a, b) {
|
|
8
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
9
|
+
if (typeof a === "string" && typeof b === "string") return a < b ? -1 : a > b ? 1 : 0;
|
|
10
|
+
if (typeof a === "bigint" && typeof b === "bigint") return a < b ? -1 : a > b ? 1 : 0;
|
|
11
|
+
if (a === b) return 0;
|
|
12
|
+
return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// packages/@columnus/shared/src/queryExecution.ts
|
|
16
|
+
function documentMatchesPredicates(doc, predicates) {
|
|
17
|
+
for (const predicate of predicates) {
|
|
18
|
+
if (!documentMatchesPredicate(doc, predicate)) return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
function documentMatchesPredicate(doc, predicate) {
|
|
23
|
+
const value = doc[predicate.field];
|
|
24
|
+
switch (predicate.op) {
|
|
25
|
+
case "eq":
|
|
26
|
+
return value === predicate.value;
|
|
27
|
+
case "neq":
|
|
28
|
+
return value !== predicate.value;
|
|
29
|
+
case "in":
|
|
30
|
+
return predicate.value.some((candidate) => candidate === value);
|
|
31
|
+
case "gt":
|
|
32
|
+
return compareOrdered(value, predicate.value) > 0;
|
|
33
|
+
case "gte":
|
|
34
|
+
return compareOrdered(value, predicate.value) >= 0;
|
|
35
|
+
case "lt":
|
|
36
|
+
return compareOrdered(value, predicate.value) < 0;
|
|
37
|
+
case "lte":
|
|
38
|
+
return compareOrdered(value, predicate.value) <= 0;
|
|
39
|
+
case "between":
|
|
40
|
+
return matchesBetween(value, predicate.value);
|
|
41
|
+
case "containsAll":
|
|
42
|
+
return Array.isArray(value) && predicate.value.every((candidate) => value.includes(candidate));
|
|
43
|
+
default: {
|
|
44
|
+
const exhaustive = predicate;
|
|
45
|
+
return exhaustive;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function sortDocuments(docs, order) {
|
|
50
|
+
const direction = order.direction === "desc" ? -1 : 1;
|
|
51
|
+
return [...docs].sort(
|
|
52
|
+
(left, right) => direction * compareOrdered(left[order.field], right[order.field])
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { documentMatchesPredicate, documentMatchesPredicates, sortDocuments };
|
|
57
|
+
//# sourceMappingURL=queryExecution.js.map
|
|
58
|
+
//# sourceMappingURL=queryExecution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queryBetween.ts","../src/queryExecution.ts"],"names":[],"mappings":"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","file":"queryExecution.js","sourcesContent":["export type BetweenValue = {\n min: unknown;\n max: unknown;\n};\n\nexport type BetweenIndexBound =\n | { op: \"gte\"; value: unknown }\n | { op: \"lte\"; value: unknown }\n | { op: \"between\"; min: unknown; max: unknown };\n\nexport function isCompleteBetween(value: BetweenValue): boolean {\n return value.min != null && value.max != null;\n}\n\n/** Convert inclusive `between` UI state into the strongest available index range. */\nexport function betweenToIndexBound(value: BetweenValue): BetweenIndexBound | null {\n if (isCompleteBetween(value)) {\n return { op: \"between\", min: value.min, max: value.max };\n }\n if (value.min != null) return { op: \"gte\", value: value.min };\n if (value.max != null) return { op: \"lte\", value: value.max };\n return null;\n}\n\nexport function matchesBetween(value: unknown, between: BetweenValue): boolean {\n if (between.min != null && compareOrdered(value, between.min) < 0) return false;\n if (between.max != null && compareOrdered(value, between.max) > 0) return false;\n return true;\n}\n\nexport function compareOrdered(a: unknown, b: unknown): number {\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"string\" && typeof b === \"string\") return a < b ? -1 : a > b ? 1 : 0;\n if (typeof a === \"bigint\" && typeof b === \"bigint\") return a < b ? -1 : a > b ? 1 : 0;\n if (a === b) return 0;\n return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;\n}\n","import { compareOrdered, matchesBetween } from \"./queryBetween.ts\";\nimport type { Predicate, QueryOrder } from \"./queryTypes.ts\";\n\n/** Evaluate residual predicates against a document. */\nexport function documentMatchesPredicates(\n doc: Record<string, unknown>,\n predicates: Predicate[],\n): boolean {\n for (const predicate of predicates) {\n if (!documentMatchesPredicate(doc, predicate)) return false;\n }\n return true;\n}\n\nexport function documentMatchesPredicate(\n doc: Record<string, unknown>,\n predicate: Predicate,\n): boolean {\n const value = doc[predicate.field];\n switch (predicate.op) {\n case \"eq\":\n return value === predicate.value;\n case \"neq\":\n return value !== predicate.value;\n case \"in\":\n return predicate.value.some((candidate) => candidate === value);\n case \"gt\":\n return compareOrdered(value, predicate.value) > 0;\n case \"gte\":\n return compareOrdered(value, predicate.value) >= 0;\n case \"lt\":\n return compareOrdered(value, predicate.value) < 0;\n case \"lte\":\n return compareOrdered(value, predicate.value) <= 0;\n case \"between\":\n return matchesBetween(value, predicate.value);\n case \"containsAll\":\n return (\n Array.isArray(value) && predicate.value.every((candidate) => value.includes(candidate))\n );\n default: {\n const exhaustive: never = predicate;\n return exhaustive;\n }\n }\n}\n\nexport function sortDocuments<T extends Record<string, unknown>>(\n docs: T[],\n order: QueryOrder,\n): T[] {\n const direction = order.direction === \"desc\" ? -1 : 1;\n return [...docs].sort(\n (left, right) => direction * compareOrdered(left[order.field], right[order.field]),\n );\n}\n"]}
|