Single-Document Patching Examples: Advanced Scripts
-
This article is part of the single-document patching examples.
It focuses on advanced patch-script techniques, including inline string compilation and cryptographic operations. -
For details about the available patching interfaces (Session API, Session API using defer, and Operations API),
their full syntax, and failure handling, see Patch a Single Document: API Overview. -
Patching examples in this article:
-
Patch failures:
If an unhandled error occurs while compiling or executing a patch script,
the patch is aborted and no partial document changes are persisted.
Patching examples
Patching using inline string compilation
-
JavaScript patches submitted through
Session.Advanced.Deferor the Operations API can compile and execute code stored in a string by usingnew Function(...)oreval(...). -
String compilation is disabled by default.
To enable it, set the Patching.AllowStringCompilation configuration key totrue. -
Compile only strings controlled by your application. Do not build compiled code from untrusted input.
- new Function() - defer
- eval() - operations
session.Advanced.Defer(new PatchCommandData(
id: "products/1-A",
changeVector: null,
patch: new PatchRequest
{
Script = @"
// Build the function body as a string.
const functionBody =
'return doc.UnitsInStock < lowStock ' +
'? doc.PricePerUnit * discount ' +
': doc.PricePerUnit;';
// Compile the string, then execute the generated function.
const calculatePrice = new Function(
'doc',
'lowStock',
'discount',
functionBody);
this.PricePerUnit = calculatePrice(
this,
args.LowStock,
args.Discount);",
Values =
{
{ "LowStock", 10 },
{ "Discount", 0.8 }
}
},
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "products/1-A",
changeVector: null,
patch: new PatchRequest
{
Script = @"
// Build an expression that can access the document and patch arguments.
const discountExpression =
'this.UnitsInStock < args.LowStock ' +
'? this.PricePerUnit * args.Discount ' +
': this.PricePerUnit';
// Evaluate the expression and assign its result.
this.PricePerUnit = eval(discountExpression);",
Values =
{
{ "LowStock", 10 },
{ "Discount", 0.8 }
}
},
patchIfMissing: null));
Generate a random UUID
Use crypto.randomUUID() to generate a UUID v4 string.
The following patch sets ExternalId only when the field has no value, so running the patch again preserves an existing ID.
For the full list of supported cryptographic methods, see JavaScript Engine - Cryptographic methods.
- Session API - defer
- Operations API
var patchRequest = new PatchRequest
{
Script = @"
// Preserve an existing ExternalId; generate one only when it is not set.
if (!this.ExternalId) {
this.ExternalId = crypto.randomUUID();
}"
};
session.Advanced.Defer(new PatchCommandData(
id: "companies/1-A",
changeVector: null,
patch: patchRequest,
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "companies/1-A",
changeVector: null,
patch: new PatchRequest
{
Script = @"
// Preserve an existing ExternalId; generate one only when it is not set.
if (!this.ExternalId) {
this.ExternalId = crypto.randomUUID();
}"
},
patchIfMissing: null));
Store a SHA-256 hash of a field
Use crypto.digest() to compute a SHA-256 hash of a normalized email address and store the Base64-encoded result in a separate field.
In this example, trimming and lowercasing ensure that values differing only in casing or surrounding whitespace produce the same hash.
A deterministic, unsalted hash is not suitable for storing passwords or as a substitute for encrypting confidential values.
For the full list of supported cryptographic methods, see JavaScript Engine - Cryptographic methods.
- Session API - defer
- Operations API
var patchRequest = new PatchRequest
{
Script = @"
if (this.Email) {
// Normalize the value so casing and surrounding whitespace do not affect the hash.
const normalizedEmail = this.Email.trim().toLowerCase();
// crypto.digest returns the hash as a Base64-encoded string.
this.EmailHash = crypto.digest('SHA-256', normalizedEmail);
this.EmailHashAlgorithm = 'SHA-256';
}"
};
session.Advanced.Defer(new PatchCommandData(
id: "users/1",
changeVector: null,
patch: patchRequest,
patchIfMissing: null));
session.SaveChanges();
store.Operations.Send(new PatchOperation(
id: "users/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"
if (this.Email) {
// Normalize the value so casing and surrounding whitespace do not affect the hash.
const normalizedEmail = this.Email.trim().toLowerCase();
// crypto.digest returns the hash as a Base64-encoded string.
this.EmailHash = crypto.digest('SHA-256', normalizedEmail);
this.EmailHashAlgorithm = 'SHA-256';
}"
},
patchIfMissing: null));
Encrypt a field
Use crypto.encryptAesGcm() to encrypt a field value. The method returns the encrypted value as a Base64-encoded string.
Decrypting the value later requires the encrypted value, the IV, and the same encryption key.
Generate a new 12-byte IV for each encryption and store it alongside the encrypted value. Load the encryption key from a secure location and do not store it in the document.
For illustration, the following code reads a Base64-encoded 256-bit AES key from an environment variable. Use your application's secret-management solution in production.
For the full list of supported cryptographic methods, see JavaScript Engine - Cryptographic methods.
- Session API - defer
- Operations API
var encryptionKey = Environment.GetEnvironmentVariable("PATCH_ENCRYPTION_KEY")
?? throw new InvalidOperationException(
"PATCH_ENCRYPTION_KEY must contain a Base64-encoded 256-bit AES key.");
var patchRequest = new PatchRequest
{
Script = @"
if (this.SensitiveData != null) {
// Generate a fresh 96-bit IV for this encryption.
const iv = crypto.getRandomValuesBase64(12);
// The key comes from PatchRequest.Values; do not store it in the document.
this.SensitiveDataEncrypted = crypto.encryptAesGcm(
iv,
args.EncryptionKey,
this.SensitiveData);
this.SensitiveDataIv = iv;
// Remove the plaintext only after encryption succeeds.
delete this.SensitiveData;
}",
Values =
{
{ "EncryptionKey", encryptionKey }
}
};
session.Advanced.Defer(new PatchCommandData(
id: "users/1",
changeVector: null,
patch: patchRequest,
patchIfMissing: null));
session.SaveChanges();
var encryptionKey = Environment.GetEnvironmentVariable("PATCH_ENCRYPTION_KEY")
?? throw new InvalidOperationException(
"PATCH_ENCRYPTION_KEY must contain a Base64-encoded 256-bit AES key.");
store.Operations.Send(new PatchOperation(
id: "users/1",
changeVector: null,
patch: new PatchRequest
{
Script = @"
if (this.SensitiveData != null) {
// Generate a fresh 96-bit IV for this encryption.
const iv = crypto.getRandomValuesBase64(12);
// The key comes from PatchRequest.Values; do not store it in the document.
this.SensitiveDataEncrypted = crypto.encryptAesGcm(
iv,
args.EncryptionKey,
this.SensitiveData);
this.SensitiveDataIv = iv;
// Remove the plaintext only after encryption succeeds.
delete this.SensitiveData;
}",
Values =
{
{ "EncryptionKey", encryptionKey }
}
},
patchIfMissing: null));