parse-stack-next 5.5.6 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +634 -0
- data/README.md +378 -6
- data/docs/caching.md +748 -0
- data/examples/basic_client.rb +3 -3
- data/examples/basic_server.rb +3 -3
- data/examples/live_query_listener.rb +12 -12
- data/examples/rag_chatbot.rb +14 -14
- data/examples/transaction_example.rb +44 -45
- data/examples/webhook_server.rb +3 -3
- data/lib/parse/access.rb +493 -0
- data/lib/parse/acl_scope.rb +125 -52
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/cancellation_token.rb +3 -3
- data/lib/parse/agent/constraint_translator.rb +4 -4
- data/lib/parse/agent/describe.rb +34 -34
- data/lib/parse/agent/errors.rb +9 -9
- data/lib/parse/agent/mcp_client.rb +61 -59
- data/lib/parse/agent/mcp_dispatcher.rb +89 -101
- data/lib/parse/agent/mcp_rack_app.rb +92 -93
- data/lib/parse/agent/mcp_server.rb +1 -1
- data/lib/parse/agent/mcp_subscriptions.rb +22 -22
- data/lib/parse/agent/metadata_audit.rb +1 -2
- data/lib/parse/agent/metadata_dsl.rb +17 -7
- data/lib/parse/agent/metadata_registry.rb +12 -12
- data/lib/parse/agent/prompt_hardening.rb +4 -4
- data/lib/parse/agent/prompts.rb +10 -10
- data/lib/parse/agent/result_formatter.rb +4 -4
- data/lib/parse/agent/tools.rb +493 -400
- data/lib/parse/agent.rb +157 -116
- data/lib/parse/api/hooks.rb +1 -1
- data/lib/parse/api/server.rb +2 -2
- data/lib/parse/api/users.rb +2 -2
- data/lib/parse/atlas_search/index_manager.rb +1 -1
- data/lib/parse/atlas_search/session.rb +40 -218
- data/lib/parse/atlas_search.rb +181 -60
- data/lib/parse/authorization.rb +466 -0
- data/lib/parse/cache/invalidation.rb +167 -0
- data/lib/parse/cache/keyspace.rb +306 -0
- data/lib/parse/cache/moneta_surface.rb +126 -0
- data/lib/parse/cache/pool.rb +48 -5
- data/lib/parse/cache/redis.rb +425 -10
- data/lib/parse/cache/scoped_view.rb +443 -0
- data/lib/parse/cache/sub_cache.rb +264 -0
- data/lib/parse/cache/upstream_roles.rb +230 -0
- data/lib/parse/client/authentication.rb +1 -1
- data/lib/parse/client/body_builder.rb +1 -2
- data/lib/parse/client/caching.rb +84 -5
- data/lib/parse/client.rb +327 -189
- data/lib/parse/clp_scope.rb +225 -28
- data/lib/parse/console.rb +3 -3
- data/lib/parse/embeddings/batch_embedder.rb +3 -3
- data/lib/parse/embeddings/binding_audit.rb +211 -0
- data/lib/parse/embeddings/cache.rb +17 -17
- data/lib/parse/embeddings/cohere.rb +21 -22
- data/lib/parse/embeddings/image_fetch.rb +22 -21
- data/lib/parse/embeddings/jina.rb +21 -21
- data/lib/parse/embeddings/local_http.rb +6 -7
- data/lib/parse/embeddings/media_file.rb +137 -0
- data/lib/parse/embeddings/openai.rb +5 -5
- data/lib/parse/embeddings/provider.rb +40 -1
- data/lib/parse/embeddings/qwen.rb +10 -10
- data/lib/parse/embeddings/spend_cap.rb +2 -3
- data/lib/parse/embeddings/streaming_body.rb +170 -0
- data/lib/parse/embeddings/video_source.rb +121 -0
- data/lib/parse/embeddings/voyage.rb +554 -143
- data/lib/parse/embeddings.rb +83 -27
- data/lib/parse/graphql/type_generator.rb +1 -1
- data/lib/parse/graphql.rb +5 -5
- data/lib/parse/live_query/client.rb +1 -1
- data/lib/parse/live_query.rb +1 -1
- data/lib/parse/lock.rb +15 -18
- data/lib/parse/lock_backend.rb +1 -1
- data/lib/parse/lookup_rewriter.rb +6 -6
- data/lib/parse/model/classes/role.rb +227 -28
- data/lib/parse/model/classes/user.rb +60 -1
- data/lib/parse/model/clp.rb +4 -4
- data/lib/parse/model/core/actions.rb +97 -15
- data/lib/parse/model/core/create_lock.rb +0 -2
- data/lib/parse/model/core/describe.rb +57 -57
- data/lib/parse/model/core/embed_managed.rb +43 -19
- data/lib/parse/model/core/fetching.rb +2 -0
- data/lib/parse/model/core/indexing.rb +14 -14
- data/lib/parse/model/core/parse_reference.rb +1 -1
- data/lib/parse/model/core/properties.rb +39 -3
- data/lib/parse/model/core/querying.rb +1 -1
- data/lib/parse/model/core/schema.rb +2 -2
- data/lib/parse/model/core/search_indexing.rb +2 -2
- data/lib/parse/model/core/vector_searchable.rb +34 -8
- data/lib/parse/model/file.rb +21 -15
- data/lib/parse/model/geojson.rb +2 -2
- data/lib/parse/model/geopoint.rb +1 -0
- data/lib/parse/model/object.rb +132 -66
- data/lib/parse/model/pointer.rb +2 -0
- data/lib/parse/model/polygon.rb +3 -6
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/model/vector.rb +3 -1
- data/lib/parse/mongodb.rb +512 -282
- data/lib/parse/pipeline_security.rb +3 -0
- data/lib/parse/query/constraints.rb +46 -46
- data/lib/parse/query.rb +146 -59
- data/lib/parse/retrieval/agent_tool.rb +34 -20
- data/lib/parse/retrieval/chunk.rb +1 -0
- data/lib/parse/retrieval/reranker/cohere.rb +11 -11
- data/lib/parse/retrieval/reranker.rb +3 -4
- data/lib/parse/retrieval/retriever.rb +4 -6
- data/lib/parse/schema/index_migrator.rb +26 -26
- data/lib/parse/schema/search_index_migrator.rb +19 -19
- data/lib/parse/stack/tasks.rb +6 -6
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +10 -10
- data/lib/parse/vector_search/hybrid.rb +166 -33
- data/lib/parse/vector_search.rb +141 -13
- data/lib/parse/webhooks/payload.rb +1 -0
- data/lib/parse/webhooks/registration.rb +4 -4
- data/lib/parse/webhooks/trigger_audit.rb +50 -49
- data/lib/parse/webhooks.rb +22 -4
- data/parse-stack-next.gemspec +6 -6
- metadata +14 -1
data/lib/parse/embeddings.rb
CHANGED
|
@@ -104,6 +104,7 @@ module Parse
|
|
|
104
104
|
class InvalidImageURL < Error
|
|
105
105
|
# @return [Symbol] failure-mode tag.
|
|
106
106
|
attr_reader :reason
|
|
107
|
+
|
|
107
108
|
def initialize(reason, message)
|
|
108
109
|
@reason = reason
|
|
109
110
|
super(message)
|
|
@@ -132,6 +133,7 @@ module Parse
|
|
|
132
133
|
end
|
|
133
134
|
super(name.to_sym, provider)
|
|
134
135
|
end
|
|
136
|
+
|
|
135
137
|
alias_method :store, :[]=
|
|
136
138
|
end
|
|
137
139
|
|
|
@@ -247,6 +249,9 @@ module Parse
|
|
|
247
249
|
@configuration = nil
|
|
248
250
|
@allowed_image_hosts = nil
|
|
249
251
|
@allowed_image_types = nil
|
|
252
|
+
@allowed_video_types = nil
|
|
253
|
+
@max_media_bytes = nil
|
|
254
|
+
BindingAudit.reset!
|
|
250
255
|
@trust_provider_url_fetch = nil
|
|
251
256
|
end
|
|
252
257
|
end
|
|
@@ -323,6 +328,54 @@ module Parse
|
|
|
323
328
|
@allowed_image_types ||= ImageFetch::DEFAULT_ALLOWED_IMAGE_TYPES
|
|
324
329
|
end
|
|
325
330
|
|
|
331
|
+
# Configure the MIME types {VideoSource.verify!} accepts after
|
|
332
|
+
# magic-byte sniffing. Defaults to
|
|
333
|
+
# {VideoSource::DEFAULT_ALLOWED_VIDEO_TYPES}, which is MP4 only —
|
|
334
|
+
# the sole container Voyage accepts. As with images, the sniffed
|
|
335
|
+
# type — never the `Content-Type` header — is what gets checked.
|
|
336
|
+
#
|
|
337
|
+
# Widening this does NOT widen what a provider accepts: the
|
|
338
|
+
# Voyage adapter still enforces its own format and size limits.
|
|
339
|
+
#
|
|
340
|
+
# @param types [Array<String>] MIME type strings.
|
|
341
|
+
# @return [Array<String>]
|
|
342
|
+
def allowed_video_types=(types)
|
|
343
|
+
unless types.is_a?(Array) && !types.empty? &&
|
|
344
|
+
types.all? { |t| t.is_a?(String) && t.include?("/") }
|
|
345
|
+
raise ArgumentError,
|
|
346
|
+
"Parse::Embeddings.allowed_video_types= expects a non-empty Array of " \
|
|
347
|
+
"MIME type Strings (got #{types.inspect})."
|
|
348
|
+
end
|
|
349
|
+
CONFIG_MUTEX.synchronize { @allowed_video_types = types.dup.freeze }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# @return [Array<String>] MIME allowlist for video bytes (frozen).
|
|
353
|
+
def allowed_video_types
|
|
354
|
+
@allowed_video_types ||= VideoSource::DEFAULT_ALLOWED_VIDEO_TYPES
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Per-file ceiling for streamed media, checked by
|
|
358
|
+
# {MediaFile}. Voyage documents 20 MB per image and 20 MB per
|
|
359
|
+
# video; the default matches. Streaming keeps an oversized file
|
|
360
|
+
# from exhausting memory, but the provider still rejects it, so
|
|
361
|
+
# failing locally turns a wasted upload into an immediate error.
|
|
362
|
+
#
|
|
363
|
+
# @param bytes [Integer] positive byte count.
|
|
364
|
+
# @return [Integer]
|
|
365
|
+
def max_media_bytes=(bytes)
|
|
366
|
+
unless bytes.is_a?(Integer) && bytes.positive?
|
|
367
|
+
raise ArgumentError,
|
|
368
|
+
"Parse::Embeddings.max_media_bytes= expects a positive Integer " \
|
|
369
|
+
"(got #{bytes.inspect})."
|
|
370
|
+
end
|
|
371
|
+
CONFIG_MUTEX.synchronize { @max_media_bytes = bytes }
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# @return [Integer] per-file ceiling for {MediaFile}, in bytes.
|
|
375
|
+
def max_media_bytes
|
|
376
|
+
@max_media_bytes ||= MediaFile::DEFAULT_MAX_MEDIA_BYTES
|
|
377
|
+
end
|
|
378
|
+
|
|
326
379
|
# Sentinel-gated opt-in for forwarding image URLs to embedding
|
|
327
380
|
# providers. Assign the exact {TRUST_PROVIDER_URL_FETCH_SENTINEL}
|
|
328
381
|
# String to unlock; any other value (including `true`, `1`,
|
|
@@ -396,8 +449,7 @@ module Parse
|
|
|
396
449
|
# @raise [InvalidImageURL] on any other validation failure.
|
|
397
450
|
def validate_image_url!(url, allow_insecure: false, mode: :forward)
|
|
398
451
|
unless mode == :fetch || trust_provider_url_fetch?
|
|
399
|
-
hint =
|
|
400
|
-
if allowed_image_hosts.empty?
|
|
452
|
+
hint = if allowed_image_hosts.empty?
|
|
401
453
|
" First populate Parse::Embeddings.allowed_image_hosts with the CDN " \
|
|
402
454
|
"hostnames you trust (currently empty — every host would be denied " \
|
|
403
455
|
"even after the sentinel is set)."
|
|
@@ -412,30 +464,30 @@ module Parse
|
|
|
412
464
|
|
|
413
465
|
unless url.is_a?(String) && !url.empty?
|
|
414
466
|
raise InvalidImageURL.new(:parse,
|
|
415
|
-
|
|
416
|
-
|
|
467
|
+
"Parse::Embeddings.validate_image_url!: url must be a non-empty String " \
|
|
468
|
+
"(got #{url.class}).")
|
|
417
469
|
end
|
|
418
470
|
|
|
419
471
|
uri = begin
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
472
|
+
URI.parse(url)
|
|
473
|
+
rescue URI::InvalidURIError => e
|
|
474
|
+
raise InvalidImageURL.new(:parse,
|
|
475
|
+
"Parse::Embeddings.validate_image_url!: invalid URL (#{e.message}).")
|
|
476
|
+
end
|
|
425
477
|
|
|
426
478
|
valid_schemes = allow_insecure ? %w[http https] : %w[https]
|
|
427
479
|
unless valid_schemes.include?(uri.scheme)
|
|
428
480
|
raise InvalidImageURL.new(:scheme,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
481
|
+
"Parse::Embeddings.validate_image_url!: scheme must be #{valid_schemes.join(" or ")} " \
|
|
482
|
+
"(got #{uri.scheme.inspect}). Forwarding non-HTTPS image URLs to a provider " \
|
|
483
|
+
"leaks any embedded query-string secrets in cleartext.")
|
|
432
484
|
end
|
|
433
485
|
|
|
434
486
|
if uri.userinfo
|
|
435
487
|
raise InvalidImageURL.new(:userinfo,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
488
|
+
"Parse::Embeddings.validate_image_url!: URL must not include userinfo " \
|
|
489
|
+
"credentials. Embedding providers will forward the full URL in their fetch " \
|
|
490
|
+
"and may log it.")
|
|
439
491
|
end
|
|
440
492
|
|
|
441
493
|
# `uri.hostname` returns the IDNA-decoded form WITHOUT IPv6
|
|
@@ -446,7 +498,7 @@ module Parse
|
|
|
446
498
|
host = uri.hostname
|
|
447
499
|
if host.nil? || host.empty?
|
|
448
500
|
raise InvalidImageURL.new(:parse,
|
|
449
|
-
|
|
501
|
+
"Parse::Embeddings.validate_image_url!: URL is missing a host.")
|
|
450
502
|
end
|
|
451
503
|
|
|
452
504
|
# Reject non-canonical IPv4 forms (decimal `2130706433`,
|
|
@@ -463,9 +515,9 @@ module Parse
|
|
|
463
515
|
# character).
|
|
464
516
|
if ip_shaped_but_not_canonical?(host)
|
|
465
517
|
raise InvalidImageURL.new(:host_blocked,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
518
|
+
"Parse::Embeddings.validate_image_url!: host #{host.inspect} is an obfuscated " \
|
|
519
|
+
"or non-canonical IP literal. Use dotted-quad IPv4 (a.b.c.d) or canonical IPv6. " \
|
|
520
|
+
"Decimal/octal/hex IP forms are refused to prevent localhost-bypass attempts.")
|
|
469
521
|
end
|
|
470
522
|
|
|
471
523
|
# **Image-host allowlist runs BEFORE the resolver hop.** Round-2
|
|
@@ -477,9 +529,9 @@ module Parse
|
|
|
477
529
|
allowed = allowed_image_hosts
|
|
478
530
|
if allowed.empty?
|
|
479
531
|
raise InvalidImageURL.new(:host_not_allowlisted,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
532
|
+
"Parse::Embeddings.validate_image_url!: Parse::Embeddings.allowed_image_hosts " \
|
|
533
|
+
"is empty — every image URL is denied. Add the CDN hostnames you trust before " \
|
|
534
|
+
"forwarding image URLs to a provider.")
|
|
483
535
|
end
|
|
484
536
|
permitted = allowed.any? do |entry|
|
|
485
537
|
if entry.start_with?(".")
|
|
@@ -491,8 +543,8 @@ module Parse
|
|
|
491
543
|
end
|
|
492
544
|
unless permitted
|
|
493
545
|
raise InvalidImageURL.new(:host_not_allowlisted,
|
|
494
|
-
|
|
495
|
-
|
|
546
|
+
"Parse::Embeddings.validate_image_url!: host #{host.inspect} not in " \
|
|
547
|
+
"Parse::Embeddings.allowed_image_hosts (#{allowed.inspect}).")
|
|
496
548
|
end
|
|
497
549
|
|
|
498
550
|
# Port allowlist runs after the host allowlist (cheap string
|
|
@@ -502,8 +554,8 @@ module Parse
|
|
|
502
554
|
require_relative "model/file"
|
|
503
555
|
unless Parse::File.allowed_remote_ports.include?(port)
|
|
504
556
|
raise InvalidImageURL.new(:port,
|
|
505
|
-
|
|
506
|
-
|
|
557
|
+
"Parse::Embeddings.validate_image_url!: port #{port} not in " \
|
|
558
|
+
"Parse::File.allowed_remote_ports.")
|
|
507
559
|
end
|
|
508
560
|
|
|
509
561
|
# CIDR + DNS resolution last — most expensive (syscall). An
|
|
@@ -516,7 +568,7 @@ module Parse
|
|
|
516
568
|
rescue ArgumentError => e
|
|
517
569
|
tag = e.message.include?("private/internal address") ? :host_blocked : :parse
|
|
518
570
|
raise InvalidImageURL.new(tag,
|
|
519
|
-
|
|
571
|
+
"Parse::Embeddings.validate_image_url!: #{e.message}")
|
|
520
572
|
end
|
|
521
573
|
|
|
522
574
|
# Return the canonicalized URL so callers store/forward
|
|
@@ -590,5 +642,9 @@ require_relative "embeddings/qwen"
|
|
|
590
642
|
require_relative "embeddings/local_http"
|
|
591
643
|
require_relative "embeddings/spend_cap"
|
|
592
644
|
require_relative "embeddings/image_fetch"
|
|
645
|
+
require_relative "embeddings/video_source"
|
|
646
|
+
require_relative "embeddings/streaming_body"
|
|
647
|
+
require_relative "embeddings/media_file"
|
|
648
|
+
require_relative "embeddings/binding_audit"
|
|
593
649
|
require_relative "embeddings/cache"
|
|
594
650
|
require_relative "embeddings/batch_embedder"
|
|
@@ -87,7 +87,7 @@ module Parse
|
|
|
87
87
|
end
|
|
88
88
|
collisions = by_gql_name.select { |_, names| names.size > 1 }
|
|
89
89
|
return if collisions.empty?
|
|
90
|
-
details = collisions.map { |gql, parse| "#{gql} ← #{parse.join(
|
|
90
|
+
details = collisions.map { |gql, parse| "#{gql} ← #{parse.join(", ")}" }.join("; ")
|
|
91
91
|
raise "Parse::GraphQL::TypeGenerator: graphql_name collisions: #{details}. " \
|
|
92
92
|
"Parse class names that differ only by underscores collapse to the same " \
|
|
93
93
|
"GraphQL type name. Rename or generate the conflicting classes separately."
|
data/lib/parse/graphql.rb
CHANGED
|
@@ -26,11 +26,11 @@ module Parse
|
|
|
26
26
|
def available?
|
|
27
27
|
return @gem_available if defined?(@gem_available)
|
|
28
28
|
@gem_available = begin
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
require "graphql"
|
|
30
|
+
true
|
|
31
|
+
rescue LoadError
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Force-reset the cached availability flag. Test-only.
|
|
@@ -364,7 +364,7 @@ module Parse
|
|
|
364
364
|
# returned subscription and register callbacks later.
|
|
365
365
|
# @return [Subscription]
|
|
366
366
|
def subscribe(class_name, where: {}, fields: nil, keys: nil, watch: nil, session_token: nil,
|
|
367
|
-
|
|
367
|
+
use_master_key: false, &block)
|
|
368
368
|
# Handle Parse::Object subclass
|
|
369
369
|
if class_name.is_a?(Class) && class_name < Parse::Object
|
|
370
370
|
class_name = class_name.parse_class
|
data/lib/parse/live_query.rb
CHANGED
|
@@ -83,7 +83,7 @@ module Parse
|
|
|
83
83
|
prefix_parts = []
|
|
84
84
|
prefix_parts << "request_id=#{request_id}" if request_id
|
|
85
85
|
prefix_parts << "class=#{class_name}" if class_name
|
|
86
|
-
prefixed = prefix_parts.empty? ? text : "#{prefix_parts.join(
|
|
86
|
+
prefixed = prefix_parts.empty? ? text : "#{prefix_parts.join(" ")} #{text}"
|
|
87
87
|
super(prefixed)
|
|
88
88
|
end
|
|
89
89
|
end
|
data/lib/parse/lock.rb
CHANGED
|
@@ -93,10 +93,10 @@ module Parse
|
|
|
93
93
|
module Lock
|
|
94
94
|
KEY_PREFIX = "parse-stack:lock:v1:"
|
|
95
95
|
|
|
96
|
-
DEFAULT_TTL
|
|
96
|
+
DEFAULT_TTL = 3
|
|
97
97
|
DEFAULT_WAIT = 2.0
|
|
98
|
-
MAX_TTL
|
|
99
|
-
MAX_WAIT
|
|
98
|
+
MAX_TTL = 30
|
|
99
|
+
MAX_WAIT = 30
|
|
100
100
|
|
|
101
101
|
# Minimum byte-length for an explicit `secret:` kwarg. 16 bytes
|
|
102
102
|
# ≈ 128 bits of separation between tenants — short enough not to
|
|
@@ -196,13 +196,13 @@ module Parse
|
|
|
196
196
|
# @raise [Parse::Lock::UnavailableError] when `on_degraded: :raise`
|
|
197
197
|
# and the store is process-local.
|
|
198
198
|
def acquire(key, ttl: DEFAULT_TTL, wait: DEFAULT_WAIT,
|
|
199
|
-
|
|
199
|
+
on_degraded: :warn, secret: :auto, &block)
|
|
200
200
|
raise ArgumentError, "block required" unless block_given?
|
|
201
201
|
validated_key = validate_key!(key)
|
|
202
202
|
validate_on_degraded!(on_degraded)
|
|
203
203
|
validate_secret!(secret)
|
|
204
|
-
normalized_ttl
|
|
205
|
-
normalized_wait = clamp(Float(wait),
|
|
204
|
+
normalized_ttl = clamp(Integer(ttl), 1, MAX_TTL)
|
|
205
|
+
normalized_wait = clamp(Float(wait), 0.0, MAX_WAIT)
|
|
206
206
|
|
|
207
207
|
# Route through Parse::LockBackend — the shared module that
|
|
208
208
|
# also serves Parse::CreateLock. The KEY_PREFIX
|
|
@@ -216,15 +216,12 @@ module Parse
|
|
|
216
216
|
# up the operator-configured secret if one exists; the
|
|
217
217
|
# explicit-String branch overrides it; the explicit-nil
|
|
218
218
|
# branch opts out without a warn.
|
|
219
|
-
resolved_secret =
|
|
220
|
-
|
|
221
|
-
when
|
|
222
|
-
when
|
|
223
|
-
when nil then nil
|
|
219
|
+
resolved_secret = case secret
|
|
220
|
+
when :auto then Parse::LockBackend.lock_secret_for(store: store, source: "Parse::Lock")
|
|
221
|
+
when String then secret
|
|
222
|
+
when nil then nil
|
|
224
223
|
end
|
|
225
|
-
digest = resolved_secret
|
|
226
|
-
? OpenSSL::HMAC.hexdigest("SHA256", resolved_secret, validated_key) \
|
|
227
|
-
: Digest::SHA256.hexdigest(validated_key)
|
|
224
|
+
digest = resolved_secret ? OpenSSL::HMAC.hexdigest("SHA256", resolved_secret, validated_key) : Digest::SHA256.hexdigest(validated_key)
|
|
228
225
|
store_key = "#{KEY_PREFIX}#{digest}"
|
|
229
226
|
|
|
230
227
|
if Parse::LockBackend.degraded_store?(store)
|
|
@@ -238,13 +235,13 @@ module Parse
|
|
|
238
235
|
# cross-process owner here — the Mutex IS the exclusion — so a
|
|
239
236
|
# fresh UUID is purely for signature parity / local fencing.
|
|
240
237
|
return Parse::LockBackend.synchronize_process_mutex(store_key) do
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
yield SecureRandom.uuid
|
|
239
|
+
end
|
|
243
240
|
end
|
|
244
241
|
|
|
245
|
-
owner
|
|
242
|
+
owner = SecureRandom.uuid
|
|
246
243
|
acquired_at = nil
|
|
247
|
-
start
|
|
244
|
+
start = Parse::LockBackend.monotonic_now
|
|
248
245
|
|
|
249
246
|
loop do
|
|
250
247
|
if Parse::LockBackend.try_acquire(store, store_key, owner, normalized_ttl)
|
data/lib/parse/lock_backend.rb
CHANGED
|
@@ -102,7 +102,7 @@ module Parse
|
|
|
102
102
|
# error (Parse::CreateLockUnavailableError vs
|
|
103
103
|
# Parse::Lock::UnavailableError) without coupling here.
|
|
104
104
|
def handle_degraded(mode, key, source: "Parse::LockBackend",
|
|
105
|
-
|
|
105
|
+
unavailable_error: nil)
|
|
106
106
|
case mode
|
|
107
107
|
when :raise
|
|
108
108
|
err = unavailable_error || Parse::Error
|
|
@@ -236,9 +236,9 @@ module Parse
|
|
|
236
236
|
mongo_local = "_p_#{pointer_field}"
|
|
237
237
|
if foreign_has_parse_reference?(target_class)
|
|
238
238
|
replace_keys(spec,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
"from" => from_collection,
|
|
240
|
+
"localField" => mongo_local,
|
|
241
|
+
"foreignField" => PARSE_REFERENCE_REMOTE)
|
|
242
242
|
else
|
|
243
243
|
as_value = read_string(spec, "as")
|
|
244
244
|
let_var = "rwLookupId_#{pointer_field}"
|
|
@@ -262,9 +262,9 @@ module Parse
|
|
|
262
262
|
mongo_foreign = "_p_#{pointer_field}"
|
|
263
263
|
if foreign_has_parse_reference?(local_class)
|
|
264
264
|
replace_keys(spec,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
"from" => from_collection,
|
|
266
|
+
"localField" => PARSE_REFERENCE_REMOTE,
|
|
267
|
+
"foreignField" => mongo_foreign)
|
|
268
268
|
else
|
|
269
269
|
as_value = read_string(spec, "as")
|
|
270
270
|
let_var = "rwReverseId_#{pointer_field}"
|